Coverage Report

Created: 2026-07-12 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/parquet/parquet_statistics.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_statistics.h"
17
18
#include <parquet/api/reader.h>
19
#include <parquet/bloom_filter.h>
20
#include <parquet/bloom_filter_reader.h>
21
#include <parquet/column_page.h>
22
#include <parquet/encoding.h>
23
#include <parquet/page_index.h>
24
#include <parquet/statistics.h>
25
#include <parquet/types.h>
26
27
#include <algorithm>
28
#include <cstddef>
29
#include <cstring>
30
#include <exception>
31
#include <limits>
32
#include <map>
33
#include <memory>
34
#include <optional>
35
#include <set>
36
#include <string>
37
#include <utility>
38
#include <vector>
39
40
#include "common/config.h"
41
#include "core/data_type/data_type.h"
42
#include "core/data_type/data_type_nullable.h"
43
#include "core/data_type_serde/data_type_serde.h"
44
#include "core/field.h"
45
#include "exprs/expr_zonemap_filter.h"
46
#include "exprs/vexpr_context.h"
47
#include "format_v2/parquet/parquet_column_schema.h"
48
#include "runtime/runtime_profile.h"
49
#include "storage/index/zone_map/zone_map_index.h"
50
#include "storage/index/zone_map/zonemap_eval_context.h"
51
52
namespace doris::format::parquet {
53
54
namespace {
55
56
enum class ParquetRowGroupPruneReason {
57
    NONE,         // cannot prune; must read
58
    STATISTICS,   // excluded by ZoneMap statistics
59
    DICTIONARY,   // excluded by dictionary
60
    BLOOM_FILTER, // excluded by bloom filter
61
};
62
63
6
bool bloom_logical_type_supported(const ParquetColumnSchema& column_schema) {
64
6
    if (column_schema.type == nullptr) {
65
0
        return false;
66
0
    }
67
6
    switch (remove_nullable(column_schema.type)->get_primitive_type()) {
68
0
    case TYPE_BOOLEAN:
69
0
    case TYPE_INT:
70
3
    case TYPE_BIGINT:
71
4
    case TYPE_FLOAT:
72
4
    case TYPE_DOUBLE:
73
6
    case TYPE_STRING:
74
6
        return true;
75
0
    default:
76
0
        return false;
77
6
    }
78
6
}
79
80
428
DecodedTimeUnit decoded_time_unit(ParquetTimeUnit time_unit) {
81
428
    switch (time_unit) {
82
0
    case ParquetTimeUnit::MILLIS:
83
0
        return DecodedTimeUnit::MILLIS;
84
2
    case ParquetTimeUnit::MICROS:
85
2
        return DecodedTimeUnit::MICROS;
86
0
    case ParquetTimeUnit::NANOS:
87
0
        return DecodedTimeUnit::NANOS;
88
426
    default:
89
426
        return DecodedTimeUnit::UNKNOWN;
90
428
    }
91
428
}
92
93
Status read_decoded_field(const ParquetColumnSchema& column_schema, DecodedColumnView view,
94
428
                          Field* field, const cctz::time_zone* timezone) {
95
428
    DORIS_CHECK(column_schema.type != nullptr);
96
428
    DORIS_CHECK(field != nullptr);
97
428
    constexpr uint8_t not_null = 0;
98
428
    view.row_count = 1;
99
428
    view.null_map = &not_null;
100
428
    view.time_unit = decoded_time_unit(column_schema.type_descriptor.time_unit);
101
428
    view.logical_integer_bit_width = column_schema.type_descriptor.integer_bit_width;
102
428
    view.logical_integer_is_signed = !column_schema.type_descriptor.is_unsigned_integer;
103
428
    view.decimal_precision = column_schema.type_descriptor.decimal_precision;
104
428
    view.decimal_scale = column_schema.type_descriptor.decimal_scale;
105
428
    view.fixed_length = column_schema.type_descriptor.fixed_length;
106
428
    view.timestamp_is_adjusted_to_utc = column_schema.type_descriptor.timestamp_is_adjusted_to_utc;
107
428
    view.timezone = timezone;
108
428
    return column_schema.type->get_serde()->read_field_from_decoded_value(*column_schema.type,
109
428
                                                                          field, view);
110
428
}
111
112
template <typename NativeType>
113
bool set_decoded_field(const ParquetColumnSchema& column_schema, DecodedValueKind value_kind,
114
426
                       const NativeType& value, Field* field, const cctz::time_zone* timezone) {
115
426
    DecodedColumnView view;
116
426
    view.value_kind = value_kind;
117
426
    view.values = reinterpret_cast<const uint8_t*>(&value);
118
426
    return read_decoded_field(column_schema, view, field, timezone).ok();
119
426
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_117set_decoded_fieldIbEEbRKNS1_19ParquetColumnSchemaENS_16DecodedValueKindERKT_PNS_5FieldEPKN4cctz9time_zoneE
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_117set_decoded_fieldIiEEbRKNS1_19ParquetColumnSchemaENS_16DecodedValueKindERKT_PNS_5FieldEPKN4cctz9time_zoneE
Line
Count
Source
114
424
                       const NativeType& value, Field* field, const cctz::time_zone* timezone) {
115
424
    DecodedColumnView view;
116
424
    view.value_kind = value_kind;
117
424
    view.values = reinterpret_cast<const uint8_t*>(&value);
118
424
    return read_decoded_field(column_schema, view, field, timezone).ok();
119
424
}
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_117set_decoded_fieldIlEEbRKNS1_19ParquetColumnSchemaENS_16DecodedValueKindERKT_PNS_5FieldEPKN4cctz9time_zoneE
Line
Count
Source
114
2
                       const NativeType& value, Field* field, const cctz::time_zone* timezone) {
115
2
    DecodedColumnView view;
116
2
    view.value_kind = value_kind;
117
2
    view.values = reinterpret_cast<const uint8_t*>(&value);
118
2
    return read_decoded_field(column_schema, view, field, timezone).ok();
119
2
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_117set_decoded_fieldIfEEbRKNS1_19ParquetColumnSchemaENS_16DecodedValueKindERKT_PNS_5FieldEPKN4cctz9time_zoneE
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_117set_decoded_fieldIdEEbRKNS1_19ParquetColumnSchemaENS_16DecodedValueKindERKT_PNS_5FieldEPKN4cctz9time_zoneE
120
121
template <typename ParquetDType>
122
bool set_decoded_min_max(const std::shared_ptr<::parquet::Statistics>& statistics,
123
                         const ParquetColumnSchema& column_schema, DecodedValueKind value_kind,
124
                         ParquetColumnStatistics* column_statistics,
125
85
                         const cctz::time_zone* timezone) {
126
85
    auto typed_statistics =
127
85
            std::static_pointer_cast<::parquet::TypedStatistics<ParquetDType>>(statistics);
128
85
    if (!set_decoded_field(column_schema, value_kind, typed_statistics->min(),
129
85
                           &column_statistics->min_value, timezone) ||
130
85
        !set_decoded_field(column_schema, value_kind, typed_statistics->max(),
131
85
                           &column_statistics->max_value, timezone)) {
132
0
        return false;
133
0
    }
134
85
    return true;
135
85
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_119set_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE0EEEEEbRKSt10shared_ptrINS4_10StatisticsEERKNS1_19ParquetColumnSchemaENS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_119set_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE1EEEEEbRKSt10shared_ptrINS4_10StatisticsEERKNS1_19ParquetColumnSchemaENS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Line
Count
Source
125
84
                         const cctz::time_zone* timezone) {
126
84
    auto typed_statistics =
127
84
            std::static_pointer_cast<::parquet::TypedStatistics<ParquetDType>>(statistics);
128
84
    if (!set_decoded_field(column_schema, value_kind, typed_statistics->min(),
129
84
                           &column_statistics->min_value, timezone) ||
130
84
        !set_decoded_field(column_schema, value_kind, typed_statistics->max(),
131
84
                           &column_statistics->max_value, timezone)) {
132
0
        return false;
133
0
    }
134
84
    return true;
135
84
}
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_119set_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE2EEEEEbRKSt10shared_ptrINS4_10StatisticsEERKNS1_19ParquetColumnSchemaENS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Line
Count
Source
125
1
                         const cctz::time_zone* timezone) {
126
1
    auto typed_statistics =
127
1
            std::static_pointer_cast<::parquet::TypedStatistics<ParquetDType>>(statistics);
128
1
    if (!set_decoded_field(column_schema, value_kind, typed_statistics->min(),
129
1
                           &column_statistics->min_value, timezone) ||
130
1
        !set_decoded_field(column_schema, value_kind, typed_statistics->max(),
131
1
                           &column_statistics->max_value, timezone)) {
132
0
        return false;
133
0
    }
134
1
    return true;
135
1
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_119set_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE4EEEEEbRKSt10shared_ptrINS4_10StatisticsEERKNS1_19ParquetColumnSchemaENS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_119set_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE5EEEEEbRKSt10shared_ptrINS4_10StatisticsEERKNS1_19ParquetColumnSchemaENS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
136
137
bool set_decoded_binary_field(const ParquetColumnSchema& column_schema, DecodedValueKind value_kind,
138
                              const StringRef& value, Field* field,
139
2
                              const cctz::time_zone* timezone) {
140
2
    std::vector<StringRef> binary_values {value};
141
2
    DecodedColumnView view;
142
2
    view.value_kind = value_kind;
143
2
    view.binary_values = &binary_values;
144
2
    return read_decoded_field(column_schema, view, field, timezone).ok();
145
2
}
146
147
bool set_string_min_max(const std::shared_ptr<::parquet::Statistics>& statistics,
148
                        const ParquetColumnSchema& column_schema,
149
                        ParquetColumnStatistics* column_statistics,
150
1
                        const cctz::time_zone* timezone) {
151
1
    switch (statistics->physical_type()) {
152
1
    case ::parquet::Type::BYTE_ARRAY: {
153
1
        auto typed_statistics =
154
1
                std::static_pointer_cast<::parquet::TypedStatistics<::parquet::ByteArrayType>>(
155
1
                        statistics);
156
1
        const auto min = ::parquet::ByteArrayToString(typed_statistics->min());
157
1
        const auto max = ::parquet::ByteArrayToString(typed_statistics->max());
158
1
        if (!set_decoded_binary_field(column_schema, DecodedValueKind::BINARY,
159
1
                                      StringRef(min.data(), min.size()),
160
1
                                      &column_statistics->min_value, timezone) ||
161
1
            !set_decoded_binary_field(column_schema, DecodedValueKind::BINARY,
162
1
                                      StringRef(max.data(), max.size()),
163
1
                                      &column_statistics->max_value, timezone)) {
164
0
            return false;
165
0
        }
166
1
        return true;
167
1
    }
168
0
    case ::parquet::Type::FIXED_LEN_BYTE_ARRAY: {
169
0
        if (column_schema.descriptor == nullptr || column_schema.descriptor->type_length() <= 0) {
170
0
            return false;
171
0
        }
172
0
        auto typed_statistics =
173
0
                std::static_pointer_cast<::parquet::TypedStatistics<::parquet::FLBAType>>(
174
0
                        statistics);
175
0
        const int type_length = column_schema.descriptor->type_length();
176
0
        const std::string min(reinterpret_cast<const char*>(typed_statistics->min().ptr),
177
0
                              type_length);
178
0
        const std::string max(reinterpret_cast<const char*>(typed_statistics->max().ptr),
179
0
                              type_length);
180
0
        if (!set_decoded_binary_field(column_schema, DecodedValueKind::FIXED_BINARY,
181
0
                                      StringRef(min.data(), min.size()),
182
0
                                      &column_statistics->min_value, timezone) ||
183
0
            !set_decoded_binary_field(column_schema, DecodedValueKind::FIXED_BINARY,
184
0
                                      StringRef(max.data(), max.size()),
185
0
                                      &column_statistics->max_value, timezone)) {
186
0
            return false;
187
0
        }
188
0
        return true;
189
0
    }
190
0
    default:
191
0
        return false;
192
1
    }
193
1
}
194
195
template <typename T>
196
3
T load_predicate_value(const char* data) {
197
3
    T value;
198
3
    memcpy(&value, data, sizeof(T));
199
3
    return value;
200
3
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIbEET_PKc
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIiEET_PKc
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIaEET_PKc
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIsEET_PKc
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIlEET_PKc
Line
Count
Source
196
3
T load_predicate_value(const char* data) {
197
3
    T value;
198
3
    memcpy(&value, data, sizeof(T));
199
3
    return value;
200
3
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIfEET_PKc
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_120load_predicate_valueIdEET_PKc
201
202
3
std::optional<int64_t> load_predicate_integral_value(const char* buf, size_t size) {
203
3
    switch (size) {
204
0
    case sizeof(int8_t):
205
0
        return static_cast<int64_t>(load_predicate_value<int8_t>(buf));
206
0
    case sizeof(int16_t):
207
0
        return static_cast<int64_t>(load_predicate_value<int16_t>(buf));
208
0
    case sizeof(int32_t):
209
0
        return static_cast<int64_t>(load_predicate_value<int32_t>(buf));
210
3
    case sizeof(int64_t):
211
3
        return load_predicate_value<int64_t>(buf);
212
0
    default:
213
0
        return std::nullopt;
214
3
    }
215
3
}
216
217
bool logical_integer_fits_physical_int32(const ParquetTypeDescriptor& type_descriptor,
218
3
                                         int64_t value) {
219
3
    const int bit_width =
220
3
            type_descriptor.integer_bit_width > 0 ? type_descriptor.integer_bit_width : 32;
221
3
    if (type_descriptor.is_unsigned_integer) {
222
3
        const uint64_t max_value = bit_width >= 32 ? std::numeric_limits<uint32_t>::max()
223
3
                                                   : ((uint64_t {1} << bit_width) - 1);
224
3
        return value >= 0 && static_cast<uint64_t>(value) <= max_value;
225
3
    }
226
0
    const int64_t min_value = bit_width >= 32 ? std::numeric_limits<int32_t>::min()
227
0
                                              : -(int64_t {1} << (bit_width - 1));
228
0
    const int64_t max_value = bit_width >= 32 ? std::numeric_limits<int32_t>::max()
229
0
                                              : ((int64_t {1} << (bit_width - 1)) - 1);
230
0
    return value >= min_value && value <= max_value;
231
3
}
232
233
std::optional<int32_t> convert_logical_integer_to_physical_int32(
234
3
        const ParquetTypeDescriptor& type_descriptor, int64_t value) {
235
3
    if (!logical_integer_fits_physical_int32(type_descriptor, value)) {
236
2
        return std::nullopt;
237
2
    }
238
1
    if (!type_descriptor.is_unsigned_integer) {
239
0
        return static_cast<int32_t>(value);
240
0
    }
241
1
    const auto unsigned_value = static_cast<uint32_t>(value);
242
1
    int32_t physical_value;
243
1
    memcpy(&physical_value, &unsigned_value, sizeof(physical_value));
244
1
    return physical_value;
245
1
}
246
247
class ArrowParquetBloomFilterAdapter final : public segment_v2::BloomFilter {
248
public:
249
    ArrowParquetBloomFilterAdapter(const ParquetColumnSchema& column_schema,
250
                                   const ::parquet::BloomFilter& bloom_filter)
251
5
            : _column_schema(column_schema), _bloom_filter(bloom_filter) {}
252
253
0
    void add_bytes(const char* buf, size_t size) override { DORIS_CHECK(false); }
254
255
5
    bool test_bytes(const char* buf, size_t size) const override {
256
5
        if (buf == nullptr) {
257
0
            return true;
258
0
        }
259
        // Parquet bloom filters are populated from the physical column carrier, while VExpr
260
        // literals are materialized as Doris logical values. Keep the logical type in
261
        // BloomFilterEvalContext for expression compatibility, and normalize to the Parquet
262
        // physical representation only at this adapter boundary.
263
5
        switch (_column_schema.type_descriptor.physical_type) {
264
0
        case ::parquet::Type::BOOLEAN:
265
0
            return test_boolean(buf, size);
266
3
        case ::parquet::Type::INT32:
267
3
            return test_physical_int32(buf, size);
268
0
        case ::parquet::Type::INT64:
269
0
            return test_int64(buf, size);
270
0
        case ::parquet::Type::FLOAT:
271
0
            return test_float(buf, size);
272
0
        case ::parquet::Type::DOUBLE:
273
0
            return test_double(buf, size);
274
0
        case ::parquet::Type::BYTE_ARRAY:
275
0
            return test_byte_array(buf, size);
276
2
        case ::parquet::Type::FIXED_LEN_BYTE_ARRAY:
277
2
            return test_fixed_len_byte_array(buf, size);
278
0
        default:
279
0
            return true;
280
5
        }
281
5
    }
282
283
0
    void set_has_null(bool has_null) override { DORIS_CHECK(!has_null); }
284
0
    bool has_null() const override { return false; }
285
0
    void add_hash(uint64_t hash) override { DORIS_CHECK(false); }
286
0
    bool test_hash(uint64_t hash) const override { return _bloom_filter.FindHash(hash); }
287
288
private:
289
0
    bool test_boolean(const char* buf, size_t size) const {
290
0
        if (size == sizeof(bool)) {
291
0
            const int32_t value = load_predicate_value<bool>(buf) ? 1 : 0;
292
0
            return _bloom_filter.FindHash(_bloom_filter.Hash(value));
293
0
        }
294
0
        if (size == sizeof(int32_t)) {
295
0
            const int32_t value = load_predicate_value<int32_t>(buf);
296
0
            return _bloom_filter.FindHash(_bloom_filter.Hash(value != 0 ? 1 : 0));
297
0
        }
298
0
        return true;
299
0
    }
300
301
3
    bool test_physical_int32(const char* buf, size_t size) const {
302
3
        const auto logical_value = load_predicate_integral_value(buf, size);
303
3
        if (!logical_value.has_value()) {
304
0
            return true;
305
0
        }
306
3
        const auto physical_value = convert_logical_integer_to_physical_int32(
307
3
                _column_schema.type_descriptor, *logical_value);
308
3
        if (!physical_value.has_value()) {
309
2
            return false;
310
2
        }
311
1
        return find_int32(*physical_value);
312
3
    }
313
314
0
    bool test_int64(const char* buf, size_t size) const {
315
0
        if (size != sizeof(int64_t)) {
316
0
            return true;
317
0
        }
318
0
        const int64_t value = load_predicate_value<int64_t>(buf);
319
0
        return _bloom_filter.FindHash(_bloom_filter.Hash(value));
320
0
    }
321
322
0
    bool test_float(const char* buf, size_t size) const {
323
0
        if (size != sizeof(float)) {
324
0
            return true;
325
0
        }
326
0
        const float value = load_predicate_value<float>(buf);
327
0
        return _bloom_filter.FindHash(_bloom_filter.Hash(value));
328
0
    }
329
330
0
    bool test_double(const char* buf, size_t size) const {
331
0
        if (size != sizeof(double)) {
332
0
            return true;
333
0
        }
334
0
        const double value = load_predicate_value<double>(buf);
335
0
        return _bloom_filter.FindHash(_bloom_filter.Hash(value));
336
0
    }
337
338
0
    bool test_byte_array(const char* buf, size_t size) const {
339
0
        ::parquet::ByteArray value(static_cast<uint32_t>(size),
340
0
                                   reinterpret_cast<const uint8_t*>(buf));
341
0
        return _bloom_filter.FindHash(_bloom_filter.Hash(&value));
342
0
    }
343
344
2
    bool test_fixed_len_byte_array(const char* buf, size_t size) const {
345
2
        if (_column_schema.type_descriptor.fixed_length <= 0) {
346
0
            return true;
347
0
        }
348
2
        if (size != static_cast<size_t>(_column_schema.type_descriptor.fixed_length)) {
349
1
            return false;
350
1
        }
351
1
        ::parquet::FLBA value(reinterpret_cast<const uint8_t*>(buf));
352
1
        return _bloom_filter.FindHash(
353
1
                _bloom_filter.Hash(&value, _column_schema.type_descriptor.fixed_length));
354
2
    }
355
356
1
    bool find_int32(int32_t value) const {
357
1
        return _bloom_filter.FindHash(_bloom_filter.Hash(value));
358
1
    }
359
360
    const ParquetColumnSchema& _column_schema;
361
    const ::parquet::BloomFilter& _bloom_filter;
362
};
363
364
6
bool bloom_filter_supported(const ParquetColumnSchema& column_schema) {
365
6
    if (!bloom_logical_type_supported(column_schema)) {
366
0
        return false;
367
0
    }
368
6
    switch (column_schema.type_descriptor.physical_type) {
369
0
    case ::parquet::Type::BOOLEAN:
370
3
    case ::parquet::Type::INT32:
371
3
    case ::parquet::Type::INT64:
372
3
    case ::parquet::Type::FLOAT:
373
3
    case ::parquet::Type::DOUBLE:
374
3
    case ::parquet::Type::BYTE_ARRAY:
375
3
        return true;
376
3
    case ::parquet::Type::FIXED_LEN_BYTE_ARRAY:
377
3
        return column_schema.type_descriptor.is_string_like &&
378
3
               column_schema.type_descriptor.fixed_length > 0;
379
0
    default:
380
0
        return false;
381
6
    }
382
6
}
383
384
bool bloom_filter_excludes(const ParquetColumnSchema& column_schema, int slot_index,
385
                           const VExprContextSPtrs& conjuncts,
386
6
                           const ::parquet::BloomFilter& bloom_filter) {
387
6
    if (!bloom_filter_supported(column_schema)) {
388
1
        return false;
389
1
    }
390
5
    ArrowParquetBloomFilterAdapter adapter(column_schema, bloom_filter);
391
5
    BloomFilterEvalContext ctx;
392
5
    ctx.slots.emplace(slot_index, BloomFilterEvalContext::SlotBloomFilter {
393
5
                                          .data_type = column_schema.type,
394
5
                                          .bloom_filter = &adapter,
395
5
                                  });
396
5
    return VExprContext::evaluate_bloom_filter(conjuncts, ctx) == ZoneMapFilterResult::kNoMatch;
397
6
}
398
399
struct RowGroupBloomFilterCache {
400
    ::parquet::BloomFilterReader* bloom_filter_reader = nullptr;
401
    std::map<int, std::unique_ptr<::parquet::BloomFilter>> column_bloom_filters;
402
    std::set<int> loaded_columns;
403
404
    ::parquet::BloomFilter* get(int row_group_idx, int leaf_column_id,
405
0
                                ParquetPruningStats* pruning_stats) {
406
0
        if (bloom_filter_reader == nullptr || leaf_column_id < 0) {
407
0
            return nullptr;
408
0
        }
409
0
        if (loaded_columns.find(leaf_column_id) == loaded_columns.end()) {
410
0
            loaded_columns.insert(leaf_column_id);
411
0
            try {
412
0
                std::shared_ptr<::parquet::RowGroupBloomFilterReader> row_group_reader;
413
0
                if (pruning_stats != nullptr) {
414
0
                    SCOPED_RAW_TIMER(&pruning_stats->bloom_filter_read_time);
415
0
                    row_group_reader = bloom_filter_reader->RowGroup(row_group_idx);
416
0
                    if (row_group_reader != nullptr) {
417
0
                        column_bloom_filters[leaf_column_id] =
418
0
                                row_group_reader->GetColumnBloomFilter(leaf_column_id);
419
0
                    }
420
0
                } else {
421
0
                    row_group_reader = bloom_filter_reader->RowGroup(row_group_idx);
422
0
                    if (row_group_reader != nullptr) {
423
0
                        column_bloom_filters[leaf_column_id] =
424
0
                                row_group_reader->GetColumnBloomFilter(leaf_column_id);
425
0
                    }
426
0
                }
427
0
            } catch (const ::parquet::ParquetException&) {
428
0
                return nullptr;
429
0
            } catch (const std::exception&) {
430
0
                return nullptr;
431
0
            }
432
0
        }
433
0
        auto it = column_bloom_filters.find(leaf_column_id);
434
0
        return it == column_bloom_filters.end() ? nullptr : it->second.get();
435
0
    }
436
};
437
438
35
bool is_dictionary_data_encoding(::parquet::Encoding::type encoding) {
439
35
    return encoding == ::parquet::Encoding::PLAIN_DICTIONARY ||
440
35
           encoding == ::parquet::Encoding::RLE_DICTIONARY;
441
35
}
442
443
0
bool is_level_encoding(::parquet::Encoding::type encoding) {
444
0
    return encoding == ::parquet::Encoding::RLE || encoding == ::parquet::Encoding::BIT_PACKED;
445
0
}
446
447
70
bool is_data_page_type(::parquet::PageType::type page_type) {
448
70
    return page_type == ::parquet::PageType::DATA_PAGE ||
449
70
           page_type == ::parquet::PageType::DATA_PAGE_V2;
450
70
}
451
452
39
bool is_dictionary_encoded_chunk(const ::parquet::ColumnChunkMetaData& column_metadata) {
453
39
    if (!column_metadata.has_dictionary_page()) {
454
4
        return false;
455
4
    }
456
457
35
    const auto& encoding_stats = column_metadata.encoding_stats();
458
35
    if (!encoding_stats.empty()) {
459
35
        bool has_dictionary_data_page = false;
460
70
        for (const auto& encoding_stat : encoding_stats) {
461
70
            if (!is_data_page_type(encoding_stat.page_type) || encoding_stat.count <= 0) {
462
35
                continue;
463
35
            }
464
35
            if (!is_dictionary_data_encoding(encoding_stat.encoding)) {
465
0
                return false;
466
0
            }
467
35
            has_dictionary_data_page = true;
468
35
        }
469
35
        return has_dictionary_data_page;
470
35
    }
471
472
0
    bool has_dictionary_encoding = false;
473
0
    for (const auto encoding : column_metadata.encodings()) {
474
0
        if (is_dictionary_data_encoding(encoding)) {
475
0
            has_dictionary_encoding = true;
476
0
            continue;
477
0
        }
478
0
        if (!is_level_encoding(encoding)) {
479
0
            return false;
480
0
        }
481
0
    }
482
0
    return has_dictionary_encoding;
483
0
}
484
485
bool supports_dictionary_pruning(const ParquetColumnSchema& column_schema,
486
39
                                 const ::parquet::ColumnChunkMetaData& column_metadata) {
487
39
    if (column_schema.kind != ParquetColumnSchemaKind::PRIMITIVE ||
488
39
        column_schema.descriptor == nullptr || column_schema.type == nullptr) {
489
0
        return false;
490
0
    }
491
39
    if (!column_schema.type_descriptor.is_string_like) {
492
0
        return false;
493
0
    }
494
39
    if (column_metadata.type() != ::parquet::Type::BYTE_ARRAY &&
495
39
        column_metadata.type() != ::parquet::Type::FIXED_LEN_BYTE_ARRAY) {
496
0
        return false;
497
0
    }
498
39
    return true;
499
39
}
500
501
} // namespace
502
503
bool read_dictionary_words(::parquet::ParquetFileReader* file_reader, int row_group_idx,
504
                           int leaf_column_id, const ParquetColumnSchema& column_schema,
505
47
                           ParquetDictionaryWords* dict_words) {
506
47
    DORIS_CHECK(dict_words != nullptr);
507
47
    dict_words->clear();
508
47
    if (file_reader == nullptr || leaf_column_id < 0) {
509
0
        return false;
510
0
    }
511
512
47
    auto row_group_reader = file_reader->RowGroup(row_group_idx);
513
47
    if (row_group_reader == nullptr) {
514
0
        return false;
515
0
    }
516
47
    auto page_reader = row_group_reader->GetColumnPageReader(leaf_column_id);
517
47
    if (page_reader == nullptr) {
518
0
        return false;
519
0
    }
520
521
47
    std::shared_ptr<::parquet::Page> page;
522
47
    try {
523
47
        page = page_reader->NextPage();
524
47
    } catch (const ::parquet::ParquetException&) {
525
0
        return false;
526
0
    } catch (const std::exception&) {
527
0
        return false;
528
0
    }
529
47
    if (page == nullptr || page->type() != ::parquet::PageType::DICTIONARY_PAGE) {
530
0
        return false;
531
0
    }
532
47
    const auto* dictionary_page = static_cast<const ::parquet::DictionaryPage*>(page.get());
533
47
    if (dictionary_page->encoding() != ::parquet::Encoding::PLAIN &&
534
47
        dictionary_page->encoding() != ::parquet::Encoding::PLAIN_DICTIONARY) {
535
0
        return false;
536
0
    }
537
47
    const int32_t dictionary_length = dictionary_page->num_values();
538
47
    if (dictionary_length <= 0) {
539
0
        return false;
540
0
    }
541
47
    const auto* dictionary_data = dictionary_page->data();
542
47
    const int dictionary_size = dictionary_page->size();
543
544
47
    dict_words->values.reserve(static_cast<size_t>(dictionary_length));
545
47
    if (column_schema.descriptor->physical_type() == ::parquet::Type::BYTE_ARRAY) {
546
47
        auto decoder = ::parquet::MakeTypedDecoder<::parquet::ByteArrayType>(
547
47
                ::parquet::Encoding::PLAIN, column_schema.descriptor);
548
47
        decoder->SetData(dictionary_length, dictionary_data, dictionary_size);
549
47
        std::vector<::parquet::ByteArray> byte_array_values(static_cast<size_t>(dictionary_length));
550
47
        if (decoder->Decode(byte_array_values.data(), dictionary_length) != dictionary_length) {
551
0
            return false;
552
0
        }
553
167
        for (int32_t dict_idx = 0; dict_idx < dictionary_length; ++dict_idx) {
554
120
            dict_words->values.emplace_back(
555
120
                    reinterpret_cast<const char*>(byte_array_values[dict_idx].ptr),
556
120
                    byte_array_values[dict_idx].len);
557
120
        }
558
47
        dict_words->build_refs();
559
47
        return true;
560
47
    }
561
0
    if (column_schema.descriptor->physical_type() == ::parquet::Type::FIXED_LEN_BYTE_ARRAY) {
562
0
        const int type_length = column_schema.descriptor->type_length();
563
0
        if (type_length <= 0) {
564
0
            return false;
565
0
        }
566
0
        auto decoder = ::parquet::MakeTypedDecoder<::parquet::FLBAType>(::parquet::Encoding::PLAIN,
567
0
                                                                        column_schema.descriptor);
568
0
        decoder->SetData(dictionary_length, dictionary_data, dictionary_size);
569
0
        std::vector<::parquet::FixedLenByteArray> flba_values(
570
0
                static_cast<size_t>(dictionary_length));
571
0
        if (decoder->Decode(flba_values.data(), dictionary_length) != dictionary_length) {
572
0
            return false;
573
0
        }
574
0
        for (int32_t dict_idx = 0; dict_idx < dictionary_length; ++dict_idx) {
575
0
            dict_words->values.emplace_back(
576
0
                    reinterpret_cast<const char*>(flba_values[dict_idx].ptr), type_length);
577
0
        }
578
0
        dict_words->build_refs();
579
0
        return true;
580
0
    }
581
0
    return false;
582
0
}
583
584
47
std::vector<Field> dictionary_fields_from_words(const ParquetDictionaryWords& dict_words) {
585
47
    std::vector<Field> fields;
586
47
    fields.reserve(dict_words.refs.size());
587
120
    for (const auto& ref : dict_words.refs) {
588
120
        fields.push_back(Field::create_field<TYPE_STRING>(String(ref.data, ref.size)));
589
120
    }
590
47
    return fields;
591
47
}
592
593
namespace {
594
595
const ParquetColumnSchema* resolve_local_leaf_schema(
596
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& schema,
597
121
        const format::LocalColumnId file_column_id) {
598
121
    if (!file_column_id.is_valid() || file_column_id.value() >= static_cast<int>(schema.size())) {
599
0
        return nullptr;
600
0
    }
601
121
    const ParquetColumnSchema* column_schema = schema[file_column_id.value()].get();
602
121
    if (column_schema == nullptr || column_schema->kind != ParquetColumnSchemaKind::PRIMITIVE ||
603
121
        column_schema->leaf_column_id < 0 || column_schema->max_repetition_level > 0) {
604
2
        return nullptr;
605
2
    }
606
119
    return column_schema;
607
121
}
608
609
std::optional<format::LocalColumnId> file_column_id_by_block_position(
610
121
        const format::FileScanRequest& request, int block_position) {
611
156
    for (const auto& [file_column_id, local_index] : request.local_positions) {
612
156
        if (local_index.value() == block_position) {
613
121
            return file_column_id;
614
121
        }
615
156
    }
616
0
    return std::nullopt;
617
121
}
618
619
bool has_expr_zonemap_filter(const format::FileScanRequest& request,
620
378
                             const RuntimeState* runtime_state) {
621
378
    if (!expr_zonemap::is_expr_zonemap_filter_enabled(runtime_state)) {
622
0
        return false;
623
0
    }
624
378
    for (const auto& conjunct : request.conjuncts) {
625
172
        if (conjunct != nullptr && conjunct->root() != nullptr &&
626
172
            conjunct->root()->can_evaluate_zonemap_filter()) {
627
111
            return true;
628
111
        }
629
172
    }
630
267
    return false;
631
378
}
632
633
70
std::set<int> collect_expr_zonemap_slot_indexes(const VExprContextSPtrs& conjuncts) {
634
70
    std::set<int> slot_indexes;
635
77
    for (const auto& conjunct : conjuncts) {
636
77
        if (conjunct != nullptr && conjunct->root() != nullptr &&
637
77
            conjunct->root()->can_evaluate_zonemap_filter()) {
638
74
            conjunct->root()->collect_slot_column_ids(slot_indexes);
639
74
        }
640
77
    }
641
70
    return slot_indexes;
642
70
}
643
644
template <typename SlotIndexSelector>
645
std::map<int, VExprContextSPtrs> collect_conjuncts_by_single_slot(
646
360
        const VExprContextSPtrs& conjuncts, SlotIndexSelector slot_index_selector) {
647
360
    std::map<int, VExprContextSPtrs> conjuncts_by_slot;
648
360
    for (const auto& conjunct : conjuncts) {
649
166
        const auto slot_index = slot_index_selector(conjunct);
650
166
        if (slot_index >= 0) {
651
39
            conjuncts_by_slot[slot_index].push_back(conjunct);
652
39
        }
653
166
    }
654
360
    return conjuncts_by_slot;
655
360
}
656
657
std::shared_ptr<segment_v2::ZoneMap> make_zonemap_from_statistics(
658
200
        const ParquetColumnStatistics& statistics) {
659
200
    if (!statistics.has_null_count && !statistics.has_min_max) {
660
5
        return nullptr;
661
5
    }
662
195
    segment_v2::ZoneMap zone_map;
663
195
    zone_map.has_null = statistics.has_null;
664
195
    zone_map.has_not_null = statistics.has_not_null;
665
195
    if (!statistics.has_not_null) {
666
3
        return std::make_shared<segment_v2::ZoneMap>(std::move(zone_map));
667
3
    }
668
192
    if (!statistics.has_min_max) {
669
0
        return nullptr;
670
0
    }
671
192
    zone_map.min_value = statistics.min_value;
672
192
    zone_map.max_value = statistics.max_value;
673
192
    return std::make_shared<segment_v2::ZoneMap>(std::move(zone_map));
674
192
}
675
676
void add_slot_zonemap(ZoneMapEvalContext* ctx, int slot_index, const DataTypePtr& data_type,
677
200
                      std::shared_ptr<segment_v2::ZoneMap> zone_map) {
678
200
    DORIS_CHECK(ctx != nullptr);
679
200
    ZoneMapEvalContext::SlotZoneMap slot_zone_map;
680
200
    slot_zone_map.data_type = data_type;
681
200
    slot_zone_map.zone_map = std::move(zone_map);
682
200
    ctx->slots.emplace(slot_index, std::move(slot_zone_map));
683
200
}
684
685
70
void accumulate_zonemap_stats(const ZoneMapEvalContext& ctx, ParquetPruningStats* pruning_stats) {
686
70
    if (pruning_stats == nullptr) {
687
0
        return;
688
0
    }
689
70
    pruning_stats->expr_zonemap_unusable_evals += ctx.stats.unusable_zonemap_eval_count;
690
70
    pruning_stats->in_zonemap_point_check_count += ctx.stats.in_zonemap_point_check_count;
691
70
    pruning_stats->in_zonemap_range_only_count += ctx.stats.in_zonemap_range_only_count;
692
70
}
693
694
} // namespace
695
696
ParquetColumnStatistics ParquetStatisticsUtils::TransformColumnStatistics(
697
        const ParquetColumnSchema& column_schema,
698
97
        const std::shared_ptr<::parquet::Statistics>& statistics, const cctz::time_zone* timezone) {
699
97
    ParquetColumnStatistics result;
700
97
    if (statistics == nullptr) {
701
7
        return result;
702
7
    }
703
704
90
    result.has_null = statistics->HasNullCount() && statistics->null_count() > 0;
705
90
    result.has_not_null = statistics->num_values() > 0 || statistics->HasMinMax();
706
90
    result.has_null_count = statistics->HasNullCount();
707
90
    if (!result.has_not_null || !statistics->HasMinMax()) {
708
4
        return result;
709
4
    }
710
711
86
    DORIS_CHECK(column_schema.type != nullptr);
712
86
    switch (statistics->physical_type()) {
713
0
    case ::parquet::Type::BOOLEAN:
714
0
        result.has_min_max = set_decoded_min_max<::parquet::BooleanType>(
715
0
                statistics, column_schema, DecodedValueKind::BOOL, &result, timezone);
716
0
        return result;
717
84
    case ::parquet::Type::INT32:
718
84
        result.has_min_max = set_decoded_min_max<::parquet::Int32Type>(
719
84
                statistics, column_schema, decoded_value_kind(column_schema.type_descriptor),
720
84
                &result, timezone);
721
84
        return result;
722
1
    case ::parquet::Type::INT64:
723
1
        result.has_min_max = set_decoded_min_max<::parquet::Int64Type>(
724
1
                statistics, column_schema, decoded_value_kind(column_schema.type_descriptor),
725
1
                &result, timezone);
726
1
        return result;
727
0
    case ::parquet::Type::FLOAT:
728
0
        result.has_min_max = set_decoded_min_max<::parquet::FloatType>(
729
0
                statistics, column_schema, DecodedValueKind::FLOAT, &result, timezone);
730
0
        return result;
731
0
    case ::parquet::Type::DOUBLE:
732
0
        result.has_min_max = set_decoded_min_max<::parquet::DoubleType>(
733
0
                statistics, column_schema, DecodedValueKind::DOUBLE, &result, timezone);
734
0
        return result;
735
1
    case ::parquet::Type::BYTE_ARRAY:
736
1
    case ::parquet::Type::FIXED_LEN_BYTE_ARRAY:
737
1
        result.has_min_max = set_string_min_max(statistics, column_schema, &result, timezone);
738
1
        return result;
739
0
    default:
740
0
        return result;
741
86
    }
742
86
}
743
744
bool ParquetStatisticsUtils::BloomFilterExcludes(const ParquetColumnSchema& column_schema,
745
                                                 int slot_index, const VExprContextSPtrs& conjuncts,
746
6
                                                 const ::parquet::BloomFilter& bloom_filter) {
747
6
    return bloom_filter_excludes(column_schema, slot_index, conjuncts, bloom_filter);
748
6
}
749
750
namespace {
751
752
ParquetRowGroupPruneReason dictionary_prune_reason(
753
        const ::parquet::RowGroupMetaData& row_group, ::parquet::ParquetFileReader* file_reader,
754
        int row_group_idx, const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
755
190
        const format::FileScanRequest& request) {
756
190
    const auto conjuncts_by_slot = collect_conjuncts_by_single_slot(
757
190
            request.conjuncts, expr_zonemap::single_slot_dictionary_index);
758
190
    for (const auto& [slot_index, conjuncts] : conjuncts_by_slot) {
759
39
        const auto file_column_id = file_column_id_by_block_position(request, slot_index);
760
39
        if (!file_column_id.has_value()) {
761
0
            continue;
762
0
        }
763
39
        const auto* column_schema = resolve_local_leaf_schema(file_schema, *file_column_id);
764
39
        if (column_schema == nullptr || column_schema->type == nullptr) {
765
0
            continue;
766
0
        }
767
39
        DCHECK_LT(column_schema->leaf_column_id, row_group.num_columns());
768
39
        auto column_chunk = row_group.ColumnChunk(column_schema->leaf_column_id);
769
39
        if (column_chunk == nullptr ||
770
39
            !supports_dictionary_pruning(*column_schema, *column_chunk) ||
771
39
            !is_dictionary_encoded_chunk(*column_chunk)) {
772
4
            continue;
773
4
        }
774
775
35
        ParquetDictionaryWords dict_words;
776
35
        if (!read_dictionary_words(file_reader, row_group_idx, column_schema->leaf_column_id,
777
35
                                   *column_schema, &dict_words)) {
778
0
            continue;
779
0
        }
780
35
        DictionaryEvalContext ctx;
781
35
        ctx.slots.emplace(slot_index, DictionaryEvalContext::SlotDictionary {
782
35
                                              .data_type = column_schema->type,
783
35
                                              .values = dictionary_fields_from_words(dict_words),
784
35
                                      });
785
35
        if (VExprContext::evaluate_dictionary_filter(conjuncts, ctx) ==
786
35
            ZoneMapFilterResult::kNoMatch) {
787
20
            return ParquetRowGroupPruneReason::DICTIONARY;
788
20
        }
789
35
    }
790
170
    return ParquetRowGroupPruneReason::NONE;
791
190
}
792
793
ParquetRowGroupPruneReason bloom_filter_prune_reason(
794
        int row_group_idx, const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
795
        const format::FileScanRequest& request, RowGroupBloomFilterCache* bloom_filter_cache,
796
170
        ParquetPruningStats* pruning_stats) {
797
170
    if (bloom_filter_cache == nullptr) {
798
0
        return ParquetRowGroupPruneReason::NONE;
799
0
    }
800
170
    const auto conjuncts_by_slot = collect_conjuncts_by_single_slot(
801
170
            request.conjuncts, expr_zonemap::single_slot_bloom_filter_index);
802
170
    for (const auto& [slot_index, conjuncts] : conjuncts_by_slot) {
803
0
        const auto file_column_id = file_column_id_by_block_position(request, slot_index);
804
0
        if (!file_column_id.has_value()) {
805
0
            continue;
806
0
        }
807
0
        const auto* column_schema = resolve_local_leaf_schema(file_schema, *file_column_id);
808
0
        if (column_schema == nullptr || column_schema->type == nullptr ||
809
0
            !bloom_filter_supported(*column_schema)) {
810
0
            continue;
811
0
        }
812
0
        auto* bloom_filter = bloom_filter_cache->get(row_group_idx, column_schema->leaf_column_id,
813
0
                                                     pruning_stats);
814
0
        if (bloom_filter == nullptr) {
815
0
            continue;
816
0
        }
817
0
        if (ParquetStatisticsUtils::BloomFilterExcludes(*column_schema, slot_index, conjuncts,
818
0
                                                        *bloom_filter)) {
819
0
            return ParquetRowGroupPruneReason::BLOOM_FILTER;
820
0
        }
821
0
    }
822
170
    return ParquetRowGroupPruneReason::NONE;
823
170
}
824
825
void init_bloom_filter_cache(::parquet::ParquetFileReader* file_reader, bool enable_bloom_filter,
826
140
                             RowGroupBloomFilterCache* bloom_filter_cache) {
827
140
    DORIS_CHECK(bloom_filter_cache != nullptr);
828
140
    if (!enable_bloom_filter || file_reader == nullptr) {
829
18
        return;
830
18
    }
831
122
    try {
832
122
        bloom_filter_cache->bloom_filter_reader = &file_reader->GetBloomFilterReader();
833
122
    } catch (const ::parquet::ParquetException&) {
834
0
        bloom_filter_cache->bloom_filter_reader = nullptr;
835
0
    } catch (const std::exception&) {
836
0
        bloom_filter_cache->bloom_filter_reader = nullptr;
837
0
    }
838
122
}
839
840
bool check_statistics(const ::parquet::RowGroupMetaData& row_group,
841
                      const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
842
                      const format::FileScanRequest& request, ParquetPruningStats* pruning_stats,
843
70
                      const cctz::time_zone* timezone) {
844
70
    const auto slot_indexes = collect_expr_zonemap_slot_indexes(request.conjuncts);
845
70
    if (slot_indexes.empty()) {
846
0
        return false;
847
0
    }
848
849
70
    ZoneMapEvalContext ctx;
850
73
    for (const int slot_index : slot_indexes) {
851
73
        const auto file_column_id = file_column_id_by_block_position(request, slot_index);
852
73
        if (!file_column_id.has_value()) {
853
0
            continue;
854
0
        }
855
73
        const auto* column_schema = resolve_local_leaf_schema(file_schema, *file_column_id);
856
73
        if (column_schema == nullptr || column_schema->type == nullptr) {
857
1
            continue;
858
1
        }
859
860
72
        std::shared_ptr<segment_v2::ZoneMap> zone_map;
861
72
        DCHECK_LT(column_schema->leaf_column_id, row_group.num_columns());
862
72
        auto column_chunk = row_group.ColumnChunk(column_schema->leaf_column_id);
863
72
        if (column_chunk != nullptr) {
864
72
            zone_map =
865
72
                    make_zonemap_from_statistics(ParquetStatisticsUtils::TransformColumnStatistics(
866
72
                            *column_schema, column_chunk->statistics(), timezone));
867
72
        }
868
72
        add_slot_zonemap(&ctx, slot_index, column_schema->type, std::move(zone_map));
869
72
    }
870
871
70
    const auto result = VExprContext::evaluate_zonemap_filter(request.conjuncts, ctx);
872
70
    accumulate_zonemap_stats(ctx, pruning_stats);
873
70
    return result == ZoneMapFilterResult::kNoMatch;
874
70
}
875
876
Status select_row_groups_by_metadata_impl(
877
        const ::parquet::FileMetaData& metadata, ::parquet::ParquetFileReader* file_reader,
878
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
879
        const format::FileScanRequest& request, const std::vector<int>* candidate_row_groups,
880
        std::vector<int>* selected_row_groups, bool enable_bloom_filter,
881
        ParquetPruningStats* pruning_stats, const cctz::time_zone* timezone,
882
140
        const RuntimeState* runtime_state) {
883
140
    int64_t row_group_filter_time_sink = 0;
884
140
    SCOPED_RAW_TIMER(pruning_stats == nullptr ? &row_group_filter_time_sink
885
140
                                              : &pruning_stats->row_group_filter_time);
886
140
    if (selected_row_groups == nullptr) {
887
0
        return Status::InvalidArgument("selected_row_groups is null");
888
0
    }
889
140
    selected_row_groups->clear();
890
891
140
    const int num_row_groups = metadata.num_row_groups();
892
140
    if (pruning_stats != nullptr) {
893
140
        pruning_stats->total_row_groups = num_row_groups;
894
140
    }
895
140
    const auto candidate_size = candidate_row_groups == nullptr
896
140
                                        ? static_cast<size_t>(num_row_groups)
897
140
                                        : candidate_row_groups->size();
898
140
    selected_row_groups->reserve(candidate_size);
899
140
    RowGroupBloomFilterCache bloom_filter_cache;
900
140
    init_bloom_filter_cache(file_reader, enable_bloom_filter, &bloom_filter_cache);
901
356
    for (size_t candidate_idx = 0; candidate_idx < candidate_size; ++candidate_idx) {
902
216
        const int row_group_idx = candidate_row_groups == nullptr
903
216
                                          ? static_cast<int>(candidate_idx)
904
216
                                          : (*candidate_row_groups)[candidate_idx];
905
216
        DORIS_CHECK(row_group_idx >= 0);
906
216
        DORIS_CHECK(row_group_idx < num_row_groups);
907
216
        auto row_group = metadata.RowGroup(row_group_idx);
908
216
        if (row_group == nullptr) {
909
0
            selected_row_groups->push_back(row_group_idx);
910
0
            continue;
911
0
        }
912
216
        ParquetRowGroupPruneReason prune_reason = ParquetRowGroupPruneReason::NONE;
913
216
        if (has_expr_zonemap_filter(request, runtime_state) &&
914
216
            check_statistics(*row_group, file_schema, request, pruning_stats, timezone)) {
915
26
            prune_reason = ParquetRowGroupPruneReason::STATISTICS;
916
26
        }
917
918
216
        if (prune_reason == ParquetRowGroupPruneReason::NONE) {
919
190
            prune_reason = dictionary_prune_reason(*row_group, file_reader, row_group_idx,
920
190
                                                   file_schema, request);
921
190
            if (prune_reason == ParquetRowGroupPruneReason::NONE) {
922
170
                prune_reason = bloom_filter_prune_reason(row_group_idx, file_schema, request,
923
170
                                                         &bloom_filter_cache, pruning_stats);
924
170
            }
925
190
        }
926
927
216
        if (prune_reason != ParquetRowGroupPruneReason::NONE) {
928
46
            if (pruning_stats != nullptr) {
929
46
                pruning_stats->filtered_group_rows += row_group->num_rows();
930
46
                if (prune_reason == ParquetRowGroupPruneReason::STATISTICS) {
931
26
                    ++pruning_stats->filtered_row_groups_by_statistics;
932
26
                } else if (prune_reason == ParquetRowGroupPruneReason::DICTIONARY) {
933
20
                    ++pruning_stats->filtered_row_groups_by_dictionary;
934
20
                } else if (prune_reason == ParquetRowGroupPruneReason::BLOOM_FILTER) {
935
0
                    ++pruning_stats->filtered_row_groups_by_bloom_filter;
936
0
                }
937
46
            }
938
46
            continue;
939
46
        }
940
170
        selected_row_groups->push_back(row_group_idx);
941
170
    }
942
140
    return Status::OK();
943
140
}
944
945
} // namespace
946
947
Status select_row_groups_by_metadata(
948
        const ::parquet::FileMetaData& metadata, ::parquet::ParquetFileReader* file_reader,
949
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
950
        const format::FileScanRequest& request, const std::vector<int>* candidate_row_groups,
951
        std::vector<int>* selected_row_groups, bool enable_bloom_filter,
952
        ParquetPruningStats* pruning_stats, const cctz::time_zone* timezone,
953
140
        const RuntimeState* runtime_state) {
954
140
    return select_row_groups_by_metadata_impl(
955
140
            metadata, file_reader, file_schema, request, candidate_row_groups, selected_row_groups,
956
140
            enable_bloom_filter, pruning_stats, timezone, runtime_state);
957
140
}
958
959
namespace {
960
961
template <typename ParquetDType>
962
bool set_page_decoded_min_max(const std::shared_ptr<::parquet::ColumnIndex>& column_index,
963
                              const ParquetColumnSchema& column_schema, size_t page_idx,
964
                              DecodedValueKind value_kind, ParquetColumnStatistics* page_statistics,
965
128
                              const cctz::time_zone* timezone) {
966
128
    const auto typed_index =
967
128
            std::static_pointer_cast<::parquet::TypedColumnIndex<ParquetDType>>(column_index);
968
128
    if (page_idx >= typed_index->min_values().size() ||
969
128
        page_idx >= typed_index->max_values().size()) {
970
0
        return false;
971
0
    }
972
128
    if (!set_decoded_field(column_schema, value_kind, typed_index->min_values()[page_idx],
973
128
                           &page_statistics->min_value, timezone) ||
974
128
        !set_decoded_field(column_schema, value_kind, typed_index->max_values()[page_idx],
975
128
                           &page_statistics->max_value, timezone)) {
976
0
        return false;
977
0
    }
978
128
    page_statistics->has_min_max = true;
979
128
    return true;
980
128
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_124set_page_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE0EEEEEbRKSt10shared_ptrINS4_11ColumnIndexEERKNS1_19ParquetColumnSchemaEmNS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_124set_page_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE1EEEEEbRKSt10shared_ptrINS4_11ColumnIndexEERKNS1_19ParquetColumnSchemaEmNS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Line
Count
Source
965
128
                              const cctz::time_zone* timezone) {
966
128
    const auto typed_index =
967
128
            std::static_pointer_cast<::parquet::TypedColumnIndex<ParquetDType>>(column_index);
968
128
    if (page_idx >= typed_index->min_values().size() ||
969
128
        page_idx >= typed_index->max_values().size()) {
970
0
        return false;
971
0
    }
972
128
    if (!set_decoded_field(column_schema, value_kind, typed_index->min_values()[page_idx],
973
128
                           &page_statistics->min_value, timezone) ||
974
128
        !set_decoded_field(column_schema, value_kind, typed_index->max_values()[page_idx],
975
128
                           &page_statistics->max_value, timezone)) {
976
0
        return false;
977
0
    }
978
128
    page_statistics->has_min_max = true;
979
128
    return true;
980
128
}
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_124set_page_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE2EEEEEbRKSt10shared_ptrINS4_11ColumnIndexEERKNS1_19ParquetColumnSchemaEmNS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_124set_page_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE4EEEEEbRKSt10shared_ptrINS4_11ColumnIndexEERKNS1_19ParquetColumnSchemaEmNS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
Unexecuted instantiation: parquet_statistics.cpp:_ZN5doris6format7parquet12_GLOBAL__N_124set_page_decoded_min_maxIN7parquet12PhysicalTypeILNS4_4Type4typeE5EEEEEbRKSt10shared_ptrINS4_11ColumnIndexEERKNS1_19ParquetColumnSchemaEmNS_16DecodedValueKindEPNS1_23ParquetColumnStatisticsEPKN4cctz9time_zoneE
981
982
bool set_page_string_min_max(const std::shared_ptr<::parquet::ColumnIndex>& column_index,
983
                             const ParquetColumnSchema& column_schema, size_t page_idx,
984
                             ParquetColumnStatistics* page_statistics,
985
0
                             const cctz::time_zone* timezone) {
986
0
    switch (column_schema.descriptor->physical_type()) {
987
0
    case ::parquet::Type::BYTE_ARRAY: {
988
0
        const auto typed_index =
989
0
                std::static_pointer_cast<::parquet::ByteArrayColumnIndex>(column_index);
990
0
        if (page_idx >= typed_index->min_values().size() ||
991
0
            page_idx >= typed_index->max_values().size()) {
992
0
            return false;
993
0
        }
994
0
        const auto min = ::parquet::ByteArrayToString(typed_index->min_values()[page_idx]);
995
0
        const auto max = ::parquet::ByteArrayToString(typed_index->max_values()[page_idx]);
996
0
        if (!set_decoded_binary_field(column_schema, DecodedValueKind::BINARY,
997
0
                                      StringRef(min.data(), min.size()),
998
0
                                      &page_statistics->min_value, timezone) ||
999
0
            !set_decoded_binary_field(column_schema, DecodedValueKind::BINARY,
1000
0
                                      StringRef(max.data(), max.size()),
1001
0
                                      &page_statistics->max_value, timezone)) {
1002
0
            return false;
1003
0
        }
1004
0
        page_statistics->has_min_max = true;
1005
0
        return true;
1006
0
    }
1007
0
    case ::parquet::Type::FIXED_LEN_BYTE_ARRAY: {
1008
0
        const int type_length = column_schema.descriptor->type_length();
1009
0
        if (type_length <= 0) {
1010
0
            return false;
1011
0
        }
1012
0
        const auto typed_index = std::static_pointer_cast<::parquet::FLBAColumnIndex>(column_index);
1013
0
        if (page_idx >= typed_index->min_values().size() ||
1014
0
            page_idx >= typed_index->max_values().size()) {
1015
0
            return false;
1016
0
        }
1017
0
        const std::string min(
1018
0
                reinterpret_cast<const char*>(typed_index->min_values()[page_idx].ptr),
1019
0
                type_length);
1020
0
        const std::string max(
1021
0
                reinterpret_cast<const char*>(typed_index->max_values()[page_idx].ptr),
1022
0
                type_length);
1023
0
        if (!set_decoded_binary_field(column_schema, DecodedValueKind::FIXED_BINARY,
1024
0
                                      StringRef(min.data(), min.size()),
1025
0
                                      &page_statistics->min_value, timezone) ||
1026
0
            !set_decoded_binary_field(column_schema, DecodedValueKind::FIXED_BINARY,
1027
0
                                      StringRef(max.data(), max.size()),
1028
0
                                      &page_statistics->max_value, timezone)) {
1029
0
            return false;
1030
0
        }
1031
0
        page_statistics->has_min_max = true;
1032
0
        return true;
1033
0
    }
1034
0
    default:
1035
0
        return false;
1036
0
    }
1037
0
}
1038
1039
bool set_page_min_max(const std::shared_ptr<::parquet::ColumnIndex>& column_index,
1040
                      const ParquetColumnSchema& column_schema, size_t page_idx,
1041
128
                      ParquetColumnStatistics* page_statistics, const cctz::time_zone* timezone) {
1042
128
    DORIS_CHECK(column_schema.type != nullptr);
1043
128
    switch (column_schema.descriptor->physical_type()) {
1044
0
    case ::parquet::Type::BOOLEAN:
1045
0
        return set_page_decoded_min_max<::parquet::BooleanType>(column_index, column_schema,
1046
0
                                                                page_idx, DecodedValueKind::BOOL,
1047
0
                                                                page_statistics, timezone);
1048
128
    case ::parquet::Type::INT32:
1049
128
        return set_page_decoded_min_max<::parquet::Int32Type>(
1050
128
                column_index, column_schema, page_idx,
1051
128
                decoded_value_kind(column_schema.type_descriptor), page_statistics, timezone);
1052
0
    case ::parquet::Type::INT64:
1053
0
        return set_page_decoded_min_max<::parquet::Int64Type>(
1054
0
                column_index, column_schema, page_idx,
1055
0
                decoded_value_kind(column_schema.type_descriptor), page_statistics, timezone);
1056
0
    case ::parquet::Type::FLOAT:
1057
0
        return set_page_decoded_min_max<::parquet::FloatType>(column_index, column_schema, page_idx,
1058
0
                                                              DecodedValueKind::FLOAT,
1059
0
                                                              page_statistics, timezone);
1060
0
    case ::parquet::Type::DOUBLE:
1061
0
        return set_page_decoded_min_max<::parquet::DoubleType>(column_index, column_schema,
1062
0
                                                               page_idx, DecodedValueKind::DOUBLE,
1063
0
                                                               page_statistics, timezone);
1064
0
    case ::parquet::Type::BYTE_ARRAY:
1065
0
    case ::parquet::Type::FIXED_LEN_BYTE_ARRAY:
1066
0
        return set_page_string_min_max(column_index, column_schema, page_idx, page_statistics,
1067
0
                                       timezone);
1068
0
    default:
1069
0
        return false;
1070
128
    }
1071
128
}
1072
1073
bool build_page_statistics(const std::shared_ptr<::parquet::ColumnIndex>& column_index,
1074
                           const ParquetColumnSchema& column_schema, size_t page_idx,
1075
                           ParquetColumnStatistics* page_statistics,
1076
128
                           const cctz::time_zone* timezone) {
1077
128
    DORIS_CHECK(page_statistics != nullptr);
1078
128
    *page_statistics = ParquetColumnStatistics {};
1079
1080
128
    const auto& null_pages = column_index->null_pages();
1081
128
    if (!column_index->has_null_counts() || page_idx >= null_pages.size() ||
1082
128
        page_idx >= column_index->null_counts().size()) {
1083
0
        return false;
1084
0
    }
1085
1086
128
    page_statistics->has_null_count = true;
1087
128
    page_statistics->has_null = column_index->null_counts()[page_idx] > 0;
1088
128
    page_statistics->has_not_null = !null_pages[page_idx];
1089
128
    if (!page_statistics->has_not_null) {
1090
0
        return true;
1091
0
    }
1092
128
    return set_page_min_max(column_index, column_schema, page_idx, page_statistics, timezone);
1093
128
}
1094
1095
std::vector<RowRange> intersect_ranges(const std::vector<RowRange>& left,
1096
8
                                       const std::vector<RowRange>& right) {
1097
8
    std::vector<RowRange> result;
1098
8
    size_t left_idx = 0;
1099
8
    size_t right_idx = 0;
1100
15
    while (left_idx < left.size() && right_idx < right.size()) {
1101
7
        const int64_t left_start = left[left_idx].start;
1102
7
        const int64_t left_end = left_start + left[left_idx].length;
1103
7
        const int64_t right_start = right[right_idx].start;
1104
7
        const int64_t right_end = right_start + right[right_idx].length;
1105
7
        const int64_t start = std::max(left_start, right_start);
1106
7
        const int64_t end = std::min(left_end, right_end);
1107
7
        if (start < end) {
1108
7
            result.push_back(RowRange {start, end - start});
1109
7
        }
1110
7
        if (left_end < right_end) {
1111
0
            ++left_idx;
1112
7
        } else {
1113
7
            ++right_idx;
1114
7
        }
1115
7
    }
1116
8
    return result;
1117
8
}
1118
1119
7
int64_t count_range_rows(const std::vector<RowRange>& ranges) {
1120
7
    int64_t rows = 0;
1121
7
    for (const auto& range : ranges) {
1122
7
        rows += range.length;
1123
7
    }
1124
7
    return rows;
1125
7
}
1126
1127
RowRange page_row_range(const ::parquet::OffsetIndex& offset_index, size_t page_idx,
1128
180
                        int64_t row_group_rows) {
1129
180
    const auto& page_locations = offset_index.page_locations();
1130
180
    const int64_t start = page_locations[page_idx].first_row_index;
1131
180
    const int64_t end = page_idx + 1 == page_locations.size()
1132
180
                                ? row_group_rows
1133
180
                                : page_locations[page_idx + 1].first_row_index;
1134
180
    DORIS_CHECK(start >= 0);
1135
180
    DORIS_CHECK(end >= start);
1136
180
    DORIS_CHECK(end <= row_group_rows);
1137
180
    return RowRange {start, end - start};
1138
180
}
1139
1140
120
void append_row_range(const RowRange& range, std::vector<RowRange>* ranges) {
1141
120
    if (range.length == 0) {
1142
0
        return;
1143
0
    }
1144
120
    if (!ranges->empty()) {
1145
106
        auto& previous = ranges->back();
1146
106
        if (previous.start + previous.length == range.start) {
1147
104
            previous.length += range.length;
1148
104
            return;
1149
104
        }
1150
106
    }
1151
16
    ranges->push_back(range);
1152
16
}
1153
1154
std::optional<
1155
        std::pair<std::shared_ptr<::parquet::ColumnIndex>, std::shared_ptr<::parquet::OffsetIndex>>>
1156
load_page_indexes_for_slot(const std::shared_ptr<::parquet::RowGroupPageIndexReader>& row_group,
1157
                           const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
1158
                           const format::FileScanRequest& request, int slot_index,
1159
9
                           const ParquetColumnSchema** column_schema) {
1160
9
    DORIS_CHECK(column_schema != nullptr);
1161
9
    *column_schema = nullptr;
1162
9
    const auto file_column_id = file_column_id_by_block_position(request, slot_index);
1163
9
    if (!file_column_id.has_value()) {
1164
0
        return std::nullopt;
1165
0
    }
1166
9
    *column_schema = resolve_local_leaf_schema(file_schema, *file_column_id);
1167
9
    if (*column_schema == nullptr || (*column_schema)->descriptor == nullptr) {
1168
1
        return std::nullopt;
1169
1
    }
1170
1171
8
    try {
1172
8
        auto column_index = row_group->GetColumnIndex((*column_schema)->leaf_column_id);
1173
8
        auto offset_index = row_group->GetOffsetIndex((*column_schema)->leaf_column_id);
1174
8
        if (column_index == nullptr || offset_index == nullptr ||
1175
8
            column_index->null_pages().size() != offset_index->page_locations().size()) {
1176
0
            return std::nullopt;
1177
0
        }
1178
8
        return std::make_pair(std::move(column_index), std::move(offset_index));
1179
8
    } catch (const ::parquet::ParquetException&) {
1180
0
        return std::nullopt;
1181
0
    } catch (const std::exception&) {
1182
0
        return std::nullopt;
1183
0
    }
1184
8
}
1185
1186
bool select_ranges_for_expr_zonemap(
1187
        const std::shared_ptr<::parquet::RowGroupPageIndexReader>& row_group,
1188
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
1189
        const format::FileScanRequest& request, int slot_index, const VExprContextSPtrs& conjuncts,
1190
        int64_t row_group_rows, std::vector<RowRange>* ranges, ParquetPruningStats* pruning_stats,
1191
9
        const cctz::time_zone* timezone) {
1192
9
    DORIS_CHECK(ranges != nullptr);
1193
9
    if (conjuncts.empty()) {
1194
0
        return false;
1195
0
    }
1196
9
    const ParquetColumnSchema* column_schema = nullptr;
1197
9
    const auto page_indexes =
1198
9
            load_page_indexes_for_slot(row_group, file_schema, request, slot_index, &column_schema);
1199
9
    if (!page_indexes.has_value()) {
1200
1
        return false;
1201
1
    }
1202
8
    const auto& [column_index, offset_index] = *page_indexes;
1203
1204
8
    ranges->clear();
1205
8
    ZoneMapEvalStats page_stats;
1206
8
    const auto page_count = offset_index->page_locations().size();
1207
136
    for (size_t page_idx = 0; page_idx < page_count; ++page_idx) {
1208
128
        ParquetColumnStatistics page_statistics;
1209
128
        if (!build_page_statistics(column_index, *column_schema, page_idx, &page_statistics,
1210
128
                                   timezone)) {
1211
0
            ranges->clear();
1212
0
            return false;
1213
0
        }
1214
1215
128
        ZoneMapEvalContext ctx;
1216
128
        add_slot_zonemap(&ctx, slot_index, column_schema->type,
1217
128
                         make_zonemap_from_statistics(page_statistics));
1218
128
        const auto result = VExprContext::evaluate_zonemap_filter(conjuncts, ctx);
1219
128
        page_stats.merge_page_eval_stats(ctx.stats);
1220
128
        if (result == ZoneMapFilterResult::kNoMatch) {
1221
60
            continue;
1222
60
        }
1223
68
        append_row_range(page_row_range(*offset_index, page_idx, row_group_rows), ranges);
1224
68
    }
1225
8
    if (pruning_stats != nullptr) {
1226
8
        pruning_stats->expr_zonemap_unusable_evals += page_stats.unusable_zonemap_eval_count;
1227
8
        pruning_stats->in_zonemap_point_check_count += page_stats.in_zonemap_point_check_count;
1228
8
        pruning_stats->in_zonemap_range_only_count += page_stats.in_zonemap_range_only_count;
1229
8
    }
1230
8
    return true;
1231
8
}
1232
1233
112
bool ranges_intersect(const std::vector<RowRange>& ranges, const RowRange& range) {
1234
112
    const int64_t range_end = range.start + range.length;
1235
112
    for (const auto& selected_range : ranges) {
1236
112
        const int64_t selected_end = selected_range.start + selected_range.length;
1237
112
        if (selected_end <= range.start) {
1238
8
            continue;
1239
8
        }
1240
104
        if (selected_range.start >= range_end) {
1241
44
            return false;
1242
44
        }
1243
60
        return true;
1244
104
    }
1245
8
    return false;
1246
112
}
1247
1248
void collect_leaf_schemas(const ParquetColumnSchema& column_schema,
1249
                          const format::LocalColumnIndex* projection,
1250
7
                          std::vector<const ParquetColumnSchema*>* leaf_schemas) {
1251
7
    if (column_schema.kind == ParquetColumnSchemaKind::PRIMITIVE) {
1252
7
        leaf_schemas->push_back(&column_schema);
1253
7
        return;
1254
7
    }
1255
0
    for (const auto& child_schema : column_schema.children) {
1256
0
        if (!format::is_child_projected(projection, child_schema->local_id)) {
1257
0
            continue;
1258
0
        }
1259
0
        const auto* child_projection =
1260
0
                format::find_child_projection(projection, child_schema->local_id);
1261
0
        collect_leaf_schemas(*child_schema, child_projection, leaf_schemas);
1262
0
    }
1263
0
}
1264
1265
void collect_request_leaf_schemas(
1266
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
1267
        const format::FileScanRequest& request,
1268
7
        std::vector<const ParquetColumnSchema*>* leaf_schemas) {
1269
7
    std::set<int> seen_leaf_ids;
1270
7
    auto collect_projection = [&](const format::LocalColumnIndex& projection) {
1271
7
        const int32_t local_id = projection.local_id();
1272
7
        if (local_id < 0 || local_id >= static_cast<int32_t>(file_schema.size())) {
1273
0
            return;
1274
0
        }
1275
7
        std::vector<const ParquetColumnSchema*> projection_leaf_schemas;
1276
7
        collect_leaf_schemas(*file_schema[local_id], &projection, &projection_leaf_schemas);
1277
7
        for (const auto* leaf_schema : projection_leaf_schemas) {
1278
7
            DORIS_CHECK(leaf_schema != nullptr);
1279
7
            if (seen_leaf_ids.insert(leaf_schema->leaf_column_id).second) {
1280
7
                leaf_schemas->push_back(leaf_schema);
1281
7
            }
1282
7
        }
1283
7
    };
1284
7
    for (const auto& projection : request.predicate_columns) {
1285
6
        collect_projection(projection);
1286
6
    }
1287
7
    for (const auto& projection : request.non_predicate_columns) {
1288
1
        collect_projection(projection);
1289
1
    }
1290
7
}
1291
1292
bool build_page_skip_plan_for_leaf(
1293
        const std::shared_ptr<::parquet::RowGroupPageIndexReader>& row_group,
1294
        const ParquetColumnSchema& column_schema, const std::vector<RowRange>& selected_ranges,
1295
7
        int64_t row_group_rows, ParquetPageSkipPlan* page_skip_plan) {
1296
7
    DORIS_CHECK(page_skip_plan != nullptr);
1297
7
    *page_skip_plan = ParquetPageSkipPlan {};
1298
7
    if (column_schema.kind != ParquetColumnSchemaKind::PRIMITIVE ||
1299
7
        column_schema.descriptor == nullptr || column_schema.leaf_column_id < 0 ||
1300
7
        column_schema.descriptor->max_repetition_level() != 0) {
1301
0
        return false;
1302
0
    }
1303
1304
7
    std::shared_ptr<::parquet::OffsetIndex> offset_index;
1305
7
    try {
1306
7
        offset_index = row_group->GetOffsetIndex(column_schema.leaf_column_id);
1307
7
    } catch (const ::parquet::ParquetException&) {
1308
0
        return false;
1309
0
    } catch (const std::exception&) {
1310
0
        return false;
1311
0
    }
1312
7
    if (offset_index == nullptr) {
1313
0
        return false;
1314
0
    }
1315
1316
7
    const auto page_count = offset_index->page_locations().size();
1317
7
    page_skip_plan->leaf_column_id = column_schema.leaf_column_id;
1318
7
    page_skip_plan->skipped_pages.resize(page_count);
1319
7
    page_skip_plan->skipped_page_compressed_sizes.resize(page_count);
1320
7
    const auto& page_locations = offset_index->page_locations();
1321
119
    for (size_t page_idx = 0; page_idx < page_count; ++page_idx) {
1322
112
        const RowRange row_range = page_row_range(*offset_index, page_idx, row_group_rows);
1323
112
        if (row_range.length == 0 || ranges_intersect(selected_ranges, row_range)) {
1324
60
            continue;
1325
60
        }
1326
52
        page_skip_plan->skipped_pages[page_idx] = 1;
1327
52
        page_skip_plan->skipped_page_compressed_sizes[page_idx] =
1328
52
                page_locations[page_idx].compressed_page_size;
1329
52
        append_row_range(row_range, &page_skip_plan->skipped_ranges);
1330
52
    }
1331
7
    if (page_skip_plan->empty()) {
1332
0
        *page_skip_plan = ParquetPageSkipPlan {};
1333
0
        return false;
1334
0
    }
1335
7
    return true;
1336
7
}
1337
1338
void build_page_skip_plans(const std::shared_ptr<::parquet::RowGroupPageIndexReader>& row_group,
1339
                           const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
1340
                           const format::FileScanRequest& request,
1341
                           const std::vector<RowRange>& selected_ranges, int64_t row_group_rows,
1342
7
                           std::map<int, ParquetPageSkipPlan>* page_skip_plans) {
1343
7
    DORIS_CHECK(page_skip_plans != nullptr);
1344
7
    page_skip_plans->clear();
1345
7
    std::vector<const ParquetColumnSchema*> leaf_schemas;
1346
7
    collect_request_leaf_schemas(file_schema, request, &leaf_schemas);
1347
7
    for (const auto* leaf_schema : leaf_schemas) {
1348
7
        DORIS_CHECK(leaf_schema != nullptr);
1349
7
        ParquetPageSkipPlan page_skip_plan;
1350
7
        if (build_page_skip_plan_for_leaf(row_group, *leaf_schema, selected_ranges, row_group_rows,
1351
7
                                          &page_skip_plan)) {
1352
7
            page_skip_plans->emplace(page_skip_plan.leaf_column_id, std::move(page_skip_plan));
1353
7
        }
1354
7
    }
1355
7
}
1356
1357
} // namespace
1358
1359
Status select_row_group_ranges_by_page_index(
1360
        ::parquet::ParquetFileReader* file_reader,
1361
        const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
1362
        const format::FileScanRequest& request, int row_group_idx, int64_t row_group_rows,
1363
        std::vector<RowRange>* selected_ranges, std::map<int, ParquetPageSkipPlan>* page_skip_plans,
1364
        ParquetPruningStats* pruning_stats, const cctz::time_zone* timezone,
1365
163
        const RuntimeState* runtime_state) {
1366
163
    int64_t page_index_filter_time_sink = 0;
1367
163
    SCOPED_RAW_TIMER(pruning_stats == nullptr ? &page_index_filter_time_sink
1368
163
                                              : &pruning_stats->page_index_filter_time);
1369
163
    DORIS_CHECK(selected_ranges != nullptr);
1370
163
    selected_ranges->clear();
1371
163
    if (page_skip_plans != nullptr) {
1372
163
        page_skip_plans->clear();
1373
163
    }
1374
163
    if (row_group_rows <= 0) {
1375
0
        return Status::OK();
1376
0
    }
1377
163
    selected_ranges->push_back(RowRange {0, row_group_rows});
1378
163
    if (!config::enable_parquet_page_index || !has_expr_zonemap_filter(request, runtime_state) ||
1379
163
        file_reader == nullptr) {
1380
122
        return Status::OK();
1381
122
    }
1382
1383
41
    std::shared_ptr<::parquet::PageIndexReader> page_index_reader;
1384
41
    std::shared_ptr<::parquet::RowGroupPageIndexReader> row_group_index_reader;
1385
41
    try {
1386
41
        if (pruning_stats != nullptr) {
1387
41
            ++pruning_stats->page_index_read_calls;
1388
41
        }
1389
41
        {
1390
41
            int64_t read_page_index_time_sink = 0;
1391
41
            SCOPED_RAW_TIMER(pruning_stats == nullptr ? &read_page_index_time_sink
1392
41
                                                      : &pruning_stats->read_page_index_time);
1393
41
            page_index_reader = file_reader->GetPageIndexReader();
1394
41
            if (page_index_reader == nullptr) {
1395
0
                return Status::OK();
1396
0
            }
1397
41
            row_group_index_reader = page_index_reader->RowGroup(row_group_idx);
1398
41
        }
1399
41
    } catch (const ::parquet::ParquetException&) {
1400
0
        return Status::OK();
1401
0
    } catch (const std::exception&) {
1402
0
        return Status::OK();
1403
0
    }
1404
41
    if (row_group_index_reader == nullptr) {
1405
33
        return Status::OK();
1406
33
    }
1407
1408
8
    std::map<int, VExprContextSPtrs> conjuncts_by_slot;
1409
10
    for (const auto& conjunct : request.conjuncts) {
1410
10
        const auto slot_index = expr_zonemap::single_slot_zonemap_index(conjunct);
1411
10
        if (slot_index >= 0) {
1412
10
            conjuncts_by_slot[slot_index].push_back(conjunct);
1413
10
        }
1414
10
    }
1415
1416
9
    for (const auto& [slot_index, conjuncts] : conjuncts_by_slot) {
1417
9
        std::vector<RowRange> filter_ranges;
1418
9
        if (!select_ranges_for_expr_zonemap(row_group_index_reader, file_schema, request,
1419
9
                                            slot_index, conjuncts, row_group_rows, &filter_ranges,
1420
9
                                            pruning_stats, timezone)) {
1421
1
            continue;
1422
1
        }
1423
8
        *selected_ranges = intersect_ranges(*selected_ranges, filter_ranges);
1424
8
        if (selected_ranges->empty()) {
1425
1
            if (page_skip_plans != nullptr) {
1426
1
                page_skip_plans->clear();
1427
1
            }
1428
1
            if (pruning_stats != nullptr) {
1429
1
                pruning_stats->filtered_page_rows += row_group_rows;
1430
1
                ++pruning_stats->filtered_row_groups_by_page_index;
1431
1
            }
1432
1
            return Status::OK();
1433
1
        }
1434
8
    }
1435
7
    if (page_skip_plans != nullptr) {
1436
7
        build_page_skip_plans(row_group_index_reader, file_schema, request, *selected_ranges,
1437
7
                              row_group_rows, page_skip_plans);
1438
7
    }
1439
7
    if (pruning_stats != nullptr) {
1440
7
        const int64_t selected_rows = count_range_rows(*selected_ranges);
1441
7
        DORIS_CHECK(selected_rows <= row_group_rows);
1442
7
        pruning_stats->filtered_page_rows += row_group_rows - selected_rows;
1443
7
    }
1444
7
    return Status::OK();
1445
8
}
1446
1447
} // namespace doris::format::parquet