-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[feature](function) Add Trino-compatible timezone_hour and timezone_m… #66860
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
base: master
Are you sure you want to change the base?
Changes from all commits
41ddf2d
aee93c9
77bbf78
ce8eb60
05a5380
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 |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| // 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. | ||
|
|
||
| #include <cctz/time_zone.h> | ||
|
|
||
| #include <cstdint> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
|
|
||
| #include "common/status.h" | ||
| #include "core/assert_cast.h" | ||
| #include "core/block/block.h" | ||
| #include "core/block/column_numbers.h" | ||
| #include "core/column/column.h" | ||
| #include "core/column/column_const.h" | ||
| #include "core/column/column_nullable.h" | ||
| #include "core/column/column_vector.h" | ||
| #include "core/data_type/data_type.h" | ||
| #include "core/data_type/data_type_nullable.h" | ||
| #include "core/data_type/data_type_number.h" | ||
| #include "core/data_type/data_type_timestamptz.h" | ||
| #include "core/data_type/primitive_type.h" | ||
| #include "core/value/timestamptz_value.h" | ||
| #include "exprs/function_context.h" | ||
| #include "exprs/function/function.h" | ||
| #include "exprs/function/simple_function_factory.h" | ||
| #include "runtime/runtime_state.h" | ||
|
|
||
| namespace doris { | ||
|
|
||
| namespace { | ||
| constexpr int64_t SECONDS_PER_HOUR = 3600; | ||
| constexpr int64_t SECONDS_PER_MINUTE = 60; | ||
|
|
||
| // TIMESTAMPTZ values are stored as UTC instants without the input zone, so the | ||
| // offset extracted here is the offset of the session time zone at the instant. | ||
| // See TimestampTzValue for the storage design. | ||
| Status execute_timezone_offset_part(FunctionContext* context, Block& block, | ||
| const ColumnNumbers& arguments, uint32_t result, | ||
| size_t input_rows_count, bool extract_hour) { | ||
| ColumnPtr col = block.get_by_position(arguments[0]).column; | ||
| // Fast path: the framework constant path is disabled | ||
| // (use_default_implementation_for_constants() == false) so a constant | ||
| // argument must not be expanded to input_rows_count rows and re-evaluated | ||
| // per row. Compute the single value once and keep the block-local const | ||
| // shape. The result type must be non-nullable here: the framework's | ||
| // default null handling wraps the result into a ColumnNullable, which a | ||
| // ColumnConst cannot be nested in. | ||
| if (is_column_const(*col) && !block.get_by_position(result).type->is_nullable()) { | ||
| const auto& const_col = assert_cast<const ColumnConst&>(*col); | ||
| const auto& tz_column = | ||
| assert_cast<const ColumnTimeStampTz&>(*const_col.get_data_column_ptr()); | ||
| int64_t offset = tz_column.get_data()[0].utc_offset(context->state()->timezone_obj()); | ||
| int64_t value = extract_hour ? offset / SECONDS_PER_HOUR | ||
| : (offset % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE; | ||
| auto nested = ColumnInt64::create(); | ||
| nested->insert_value(value); | ||
| block.get_by_position(result).column = | ||
| ColumnConst::create(std::move(nested), input_rows_count); | ||
| return Status::OK(); | ||
| } | ||
| // Unwrap nullable and const wrappers in any nesting order so that | ||
| // ColumnNullable(ColumnConst(...)) and ColumnConst(ColumnNullable(...)) | ||
| // inputs both reach the plain ColumnTimeStampTz data below. | ||
| col = remove_nullable(col); | ||
| if (is_column_const(*col)) { | ||
| col = assert_cast<const ColumnConst&>(*col).convert_to_full_column(); | ||
| col = remove_nullable(col); | ||
| } | ||
| const auto* tz_column = assert_cast<const ColumnTimeStampTz*>(col.get()); | ||
| const auto& tz_data = tz_column->get_data(); | ||
|
|
||
| auto result_column = ColumnInt64::create(); | ||
| auto& result_data = result_column->get_data(); | ||
| result_data.resize(input_rows_count); | ||
|
|
||
| const cctz::time_zone& timezone = context->state()->timezone_obj(); | ||
|
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. [P1] Extract the input value's zone, not the session zone Trino's |
||
| for (size_t i = 0; i < input_rows_count; ++i) { | ||
| int64_t offset = tz_data[i].utc_offset(timezone); | ||
| result_data[i] = extract_hour ? offset / SECONDS_PER_HOUR | ||
| : (offset % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE; | ||
| } | ||
|
|
||
| block.get_by_position(result).column = std::move(result_column); | ||
| return Status::OK(); | ||
| } | ||
| } // namespace | ||
|
|
||
| class FunctionTimezoneHour : public IFunction { | ||
| public: | ||
| static constexpr auto name = "timezone_hour"; | ||
|
|
||
| static FunctionPtr create() { return std::make_shared<FunctionTimezoneHour>(); } | ||
|
|
||
| String get_name() const override { return name; } | ||
|
|
||
| size_t get_number_of_arguments() const override { return 1; } | ||
|
|
||
| DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { | ||
| return std::make_shared<DataTypeInt64>(); | ||
| } | ||
|
|
||
| // The result depends on the session time_zone, so a constant result must | ||
| // never be cached: the point-query short-circuit executor opens output | ||
| // expressions with the default timezone and later reuses cached constant | ||
| // columns without re-evaluating them (VectorizedFnCall::is_constant | ||
| // consults this flag). Disable it like other nondeterministic functions | ||
| // (e.g. random, uuid). | ||
| bool use_default_implementation_for_constants() const override { return false; } | ||
|
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. [P1] Apply the request timezone before constant children open This override only makes the outer call nonconstant. |
||
|
|
||
| Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, | ||
| uint32_t result, size_t input_rows_count) const override { | ||
| return execute_timezone_offset_part(context, block, arguments, result, input_rows_count, | ||
| true); | ||
| } | ||
| }; | ||
|
|
||
| class FunctionTimezoneMinute : public IFunction { | ||
| public: | ||
| static constexpr auto name = "timezone_minute"; | ||
|
|
||
| static FunctionPtr create() { return std::make_shared<FunctionTimezoneMinute>(); } | ||
|
|
||
| String get_name() const override { return name; } | ||
|
|
||
| size_t get_number_of_arguments() const override { return 1; } | ||
|
|
||
| DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { | ||
| return std::make_shared<DataTypeInt64>(); | ||
| } | ||
|
|
||
| // See FunctionTimezoneHour::use_default_implementation_for_constants. | ||
| bool use_default_implementation_for_constants() const override { return false; } | ||
|
|
||
| Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, | ||
| uint32_t result, size_t input_rows_count) const override { | ||
| return execute_timezone_offset_part(context, block, arguments, result, input_rows_count, | ||
| false); | ||
| } | ||
| }; | ||
|
|
||
| void register_function_timezone_hour_minute(SimpleFunctionFactory& factory) { | ||
| factory.register_function<FunctionTimezoneHour>(); | ||
| factory.register_function<FunctionTimezoneMinute>(); | ||
| } | ||
|
|
||
| } // namespace doris | ||
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.
[P2] Preserve block-local constness without enabling cross-request caching
Because the framework constant path is disabled, this expands a one-value
ColumnConsttoinput_rows_count, allocates a full result column, and performs the identical cctz lookup for every scanned row. A projection such astimezone_hour(CAST('2024-01-15 12:00:00' AS TIMESTAMPTZ))over a large table therefore does O(N) timezone work for one per-execution value. Keepuse_default_implementation_for_constants()false soVectorizedFnCall::is_constant()cannot cache across requests, but detect the const argument here, evaluate its nested value once, and return a block-localColumnConst; the const-input test can assert that physical shape.