Coverage Report

Created: 2026-07-12 22:20

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