Coverage Report

Created: 2026-07-13 14:43

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