-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[fix](be) Prevent DST timestamp statistics mispruning #65497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ | |
| #include <optional> | ||
| #include <set> | ||
| #include <string> | ||
| #include <type_traits> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
|
|
@@ -45,6 +46,7 @@ | |
| #include "exprs/expr_zonemap_filter.h" | ||
| #include "exprs/vexpr_context.h" | ||
| #include "format_v2/parquet/parquet_column_schema.h" | ||
| #include "format_v2/timestamp_statistics.h" | ||
| #include "runtime/runtime_profile.h" | ||
| #include "storage/index/zone_map/zone_map_index.h" | ||
| #include "storage/index/zone_map/zonemap_eval_context.h" | ||
|
|
@@ -118,13 +120,51 @@ bool set_decoded_field(const ParquetColumnSchema& column_schema, DecodedValueKin | |
| return read_decoded_field(column_schema, view, field, timezone).ok(); | ||
| } | ||
|
|
||
| int64_t floor_timestamp_seconds(int64_t value, ParquetTimeUnit time_unit) { | ||
| int64_t units_per_second = 1; | ||
| switch (time_unit) { | ||
| case ParquetTimeUnit::MILLIS: | ||
| units_per_second = 1000; | ||
| break; | ||
| case ParquetTimeUnit::MICROS: | ||
| units_per_second = 1000000; | ||
| break; | ||
| case ParquetTimeUnit::NANOS: | ||
| units_per_second = 1000000000; | ||
| break; | ||
| default: | ||
| DORIS_CHECK(false); | ||
| } | ||
| return format::floor_epoch_seconds(value, units_per_second); | ||
| } | ||
|
|
||
| bool timestamp_min_max_is_safe(const ParquetColumnSchema& column_schema, int64_t min_value, | ||
| int64_t max_value, const cctz::time_zone* timezone) { | ||
| if (!column_schema.type_descriptor.is_timestamp || | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also disables min/max for adjusted-to-UTC Parquet timestamps after |
||
| !column_schema.type_descriptor.timestamp_is_adjusted_to_utc || timezone == nullptr || | ||
| remove_nullable(column_schema.type)->get_primitive_type() == TYPE_TIMESTAMPTZ) { | ||
| // TIMESTAMPTZ keeps the original UTC ordering, so local civil-time rollback does not make | ||
| // its converted min/max non-monotonic. | ||
| return true; | ||
| } | ||
| return format::utc_timestamp_range_is_monotonic( | ||
| floor_timestamp_seconds(min_value, column_schema.type_descriptor.time_unit), | ||
| floor_timestamp_seconds(max_value, column_schema.type_descriptor.time_unit), *timezone); | ||
| } | ||
|
|
||
| template <typename ParquetDType> | ||
| bool set_decoded_min_max(const std::shared_ptr<::parquet::Statistics>& statistics, | ||
| const ParquetColumnSchema& column_schema, DecodedValueKind value_kind, | ||
| ParquetColumnStatistics* column_statistics, | ||
| const cctz::time_zone* timezone) { | ||
| auto typed_statistics = | ||
| std::static_pointer_cast<::parquet::TypedStatistics<ParquetDType>>(statistics); | ||
| if constexpr (std::is_same_v<ParquetDType, ::parquet::Int64Type>) { | ||
| if (!timestamp_min_max_is_safe(column_schema, typed_statistics->min(), | ||
| typed_statistics->max(), timezone)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (!set_decoded_field(column_schema, value_kind, typed_statistics->min(), | ||
| &column_statistics->min_value, timezone) || | ||
| !set_decoded_field(column_schema, value_kind, typed_statistics->max(), | ||
|
|
@@ -969,6 +1009,12 @@ bool set_page_decoded_min_max(const std::shared_ptr<::parquet::ColumnIndex>& col | |
| page_idx >= typed_index->max_values().size()) { | ||
| return false; | ||
| } | ||
| if constexpr (std::is_same_v<ParquetDType, ::parquet::Int64Type>) { | ||
| if (!timestamp_min_max_is_safe(column_schema, typed_index->min_values()[page_idx], | ||
| typed_index->max_values()[page_idx], timezone)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (!set_decoded_field(column_schema, value_kind, typed_index->min_values()[page_idx], | ||
| &page_statistics->min_value, timezone) || | ||
| !set_decoded_field(column_schema, value_kind, typed_index->max_values()[page_idx], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Licensed to the Apache Software Foundation (ASF) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The ASF licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <cctz/time_zone.h> | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| #include "common/check.h" | ||
|
|
||
| namespace doris::format { | ||
|
|
||
| inline int64_t floor_epoch_seconds(int64_t value, int64_t units_per_second) { | ||
| DORIS_CHECK(units_per_second > 0); | ||
| auto seconds = value / units_per_second; | ||
| if (value < 0 && value % units_per_second != 0) { | ||
| --seconds; | ||
| } | ||
| return seconds; | ||
| } | ||
|
|
||
| // UTC instants are ordered, but converting them to civil DATETIMEV2 values is not monotonic when | ||
| // a timezone moves its clock backward. Metadata min/max converted across such a transition cannot | ||
| // safely form a ZoneMap: the true civil minimum or maximum may occur inside the UTC interval. | ||
| inline bool utc_timestamp_range_is_monotonic(int64_t min_seconds, int64_t max_seconds, | ||
| const cctz::time_zone& timezone) { | ||
| DORIS_CHECK(min_seconds <= max_seconds); | ||
| auto current = cctz::time_point<cctz::seconds>(cctz::seconds(min_seconds)); | ||
| const auto range_end = cctz::time_point<cctz::seconds>(cctz::seconds(max_seconds)); | ||
| cctz::time_zone::civil_transition transition; | ||
| while (timezone.next_transition(current, &transition)) { | ||
| const auto transition_time = timezone.lookup(transition.to).trans; | ||
| if (transition_time > range_end) { | ||
| return true; | ||
| } | ||
| if (transition.to < transition.from) { | ||
| return false; | ||
| } | ||
| // Move past the transition that was just inspected. Some cctz implementations return the | ||
| // same transition again when queried at its exact instant, which would otherwise prevent | ||
| // us from seeing a later rollback in the requested range. | ||
| current = transition_time + cctz::seconds(1); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| } // namespace doris::format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only disables the ZoneMap conversion path, but ORC scan pruning still builds timestamp SearchArguments in
_init_search_argument_from_local_filters()and usesgetNeedReadStripes()before row-level filters run. For aTIMESTAMP_INSTANTcolumn decoded as session-localDATETIMEV2, a rollback value such as2021-11-07 01:30inAmerica/New_Yorknames two UTC instants; the SARG literal conversion picks a single timestamp and lets ORC compare it to stripe min/max, so a stripe containing the other repeated-hour instant can still be pruned. Please also guard or disable timestamp SARG pruning for non-monotonic local mappings, and add an ORC scan test for that rollback case.