Coverage Report

Created: 2026-07-16 19:00

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