Coverage Report

Created: 2026-07-12 17:11

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