github-actions[bot] commented on code in PR #68301:
URL: https://github.com/apache/doris/pull/68301#discussion_r4059258454


##########
be/src/core/value/timestamptz_value.cpp:
##########
@@ -38,6 +39,13 @@ std::string TimestampTzValue::to_string(const 
cctz::time_zone& tz, int scale) co
     auto lookup_result = tz.lookup(cur_tz_time);
 
     cctz::civil_second civ = lookup_result.cs;
+    // UTC storage bounds do not guarantee a representable session-local year. 
Reject
+    // overflow before DateTimeV2 formatting could produce an offset-only wire 
value.
+    if (civ.year() < 0 || civ.year() > 9999) {

Review Comment:
   [P2] Keep the remaining TIMESTAMP_NS error paths from re-entering this 
throwing formatter. With the session zone +08:00, the maximum UTC TIMESTAMPTZ 
first fails local conversion because the year becomes 10000; strict 
TIMESTAMPTZ-to-TIMESTAMP_NS cast and TIMESTAMPTZ/TIMESTAMP_NS comparison then 
call to_string(local_zone) while building their InvalidArgument Status, so this 
exception escapes before that Status is returned. The parallel cast paths 
changed in this PR already render utc_dt() instead. Please update these two 
missed callers the same way and add strict-cast and comparison boundary tests.



##########
be/src/exprs/function/cast/cast_to_datetimev2_impl.hpp:
##########
@@ -713,16 +714,39 @@ inline bool 
CastToDatetimeV2::from_string_strict_mode_internal(
                 // minute
                 SET_PARAMS_RET_FALSE_IFN((consume_digit<UInt32, 2>(ptr, end, 
part[1])),
                                          "invalid minute offset '{}'", 
std::string {ptr, end});
-                SET_PARAMS_RET_FALSE_IFN((part[1] == 0 || part[1] == 30 || 
part[1] == 45),
-                                         "invalid minute offset '{}'", 
part[1]);
+                if constexpr (type == DataTimeCastEnumType::TIMESTAMP_TZ) {
+                    // TIMESTAMPTZ output preserves historical offsets, 
including seconds and
+                    // non-quarter-hour minutes. Keep the legacy DATETIME 
parser unchanged.
+                    SET_PARAMS_RET_FALSE_IFN(part[1] < 60, "invalid minute 
offset '{}'", part[1]);
+                    if (ptr < end && *ptr == ':') {
+                        ++ptr;
+                        SET_PARAMS_RET_FALSE_IFN(
+                                (consume_digit<UInt32, 2>(ptr, end, 
second_offset)),
+                                "invalid second offset '{}'", std::string 
{ptr, end});
+                        SET_PARAMS_RET_FALSE_IFN(second_offset < 60, "invalid 
second offset '{}'",
+                                                 second_offset);
+                    }
+                } else {
+                    SET_PARAMS_RET_FALSE_IFN((part[1] == 0 || part[1] == 30 || 
part[1] == 45),
+                                             "invalid minute offset '{}'", 
part[1]);
+                }
             }
-            SET_PARAMS_RET_FALSE_IFN(part[0] != 14 || part[1] == 0, "invalid 
timezone offset '{}'",
-                                     combine_tz_offset(sign, part[0], 
part[1]));
-
-            SET_PARAMS_RET_FALSE_IFN(TimezoneUtils::find_cctz_time_zone(
-                                             combine_tz_offset(sign, part[0], 
part[1]), parsed_tz),
+            SET_PARAMS_RET_FALSE_IFN(part[0] != 14 || (part[1] == 0 && 
second_offset == 0),
                                      "invalid timezone offset '{}'",
                                      combine_tz_offset(sign, part[0], 
part[1]));
+
+            if (second_offset != 0) {

Review Comment:
   [P2] Accept every historical offset the formatter emits. 
TimestampTzValue::to_string() now emits the exact cctz offset, but both parser 
branches still cap the hour at 14 and this seconds branch additionally caps 
negative offsets at 12. IANA history includes larger values (for example 
Asia/Manila used -15:56:08 before 1845), so Doris can return a TIMESTAMPTZ 
string that it cannot parse back. Please align both parser branches with the 
formatter's range and add strict/non-strict round-trip coverage for an extreme 
historical zone.



##########
regression-test/suites/datatype_p0/timestamptz/test_timestamptz_historical_offset.groovy:
##########
@@ -0,0 +1,53 @@
+// 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.
+
+suite("test_timestamptz_historical_offset") {
+    def originalZone = sql("select @@time_zone")[0][0]
+    def originalStrict = sql("select @@enable_strict_cast")[0][0]
+    def cases = [
+        ["Asia/Shanghai", "1890-01-01 00:00:00.123456+00:00", "1890-01-01 
08:05:43.123456+08:05:43"],
+        ["America/New_York", "1880-01-01 00:00:00.123456+00:00", "1879-12-31 
19:03:58.123456-04:56:02"],
+        ["Asia/Shanghai", "2024-01-01 00:00:00.123456+00:00", "2024-01-01 
08:00:00.123456+08:00"],
+        ["America/New_York", "2024-01-01 00:00:00.123456+00:00", "2023-12-31 
19:00:00.123456-05:00"],
+        ["Asia/Kathmandu", "2024-01-01 00:00:00.123456+00:00", "2024-01-01 
05:45:00.123456+05:45"]
+    ]
+    try {
+        for (def testCase : cases) {
+            sql "set time_zone = '${testCase[0]}'"
+            for (def strict : [false, true]) {
+                sql "set enable_strict_cast = ${strict}"
+                // A nonconstant input exercises BE protocol formatting and 
parsing instead of
+                // FE constant folding. The offset must retain the instant 
when sent back by a client.
+                def wire = sql("""
+                    select cast(concat('${testCase[1]}', substring(cast(number 
as string), 2))
+                                as timestamptz(6))
+                    from numbers('number' = '1')
+                """)[0][0].toString()
+                assertEquals(testCase[2], wire)

Review Comment:
   [P2] Record these deterministic results as golden output. All three new 
TIMESTAMPTZ suites assert successful query results directly and add no .out 
files, while this repository requires determined regression results to use 
qt_/order_qt_ cases with runner-generated output. Please keep the test { sql; 
exception } blocks for failures, convert the successful cases in all three 
suites, and generate their checked-in .out files.



##########
be/src/exec/sink/writer/iceberg/partition_transformers.cpp:
##########
@@ -46,6 +46,12 @@ const std::chrono::sys_days 
PartitionColumnTransformUtils::EPOCH = std::chrono::
 std::unique_ptr<PartitionColumnTransform> PartitionColumnTransforms::create(
         const doris::iceberg::PartitionField& field, const DataTypePtr& 
source_type) {
     auto& transform = field.transform();
+    // Identity/void only carry values; computed binary partition transforms 
are unsupported.
+    if (source_type->get_primitive_type() == TYPE_VARBINARY && transform != 
"identity" &&

Review Comment:
   [P1] Reject identity until the writer supports binary partitions. This 
branch deliberately leaves identity enabled, but 
IdentityPartitionColumnTransform returns ColumnVarbinary and 
VIcebergTableWriter::_get_iceberg_partition_value has no TYPE_VARBINARY arm, so 
a non-null dynamic identity partition falls into "Unsupported type for 
partition". The later partition-string and FE commit reconstruction paths also 
have no binary-safe representation. With enable.mapping.varbinary=true, inserts 
into Iceberg BINARY/FIXED identity partitions therefore fail. Please either 
reject identity here too, or implement byte-safe 
extraction/transport/reconstruction and add an end-to-end identity test.



##########
be/src/exec/common/hash_table/hash_key_type.h:
##########
@@ -102,6 +102,13 @@ inline HashKeyType get_hash_key_type_fixed(const 
std::vector<DataTypePtr>& data_
 }
 
 inline HashKeyType get_hash_key_type(const std::vector<DataTypePtr>& 
data_types) {
+    // Reject binary before the multi-key serialization fallback can enable 
joins or grouping.
+    for (const auto& type : data_types) {
+        if (type->get_primitive_type() == TYPE_VARBINARY) {

Review Comment:
   [P1] Preserve the existing serialized path for supported multi-key 
consumers. This pre-check now runs before the multi-key branch, so it also 
rejects keys that previously selected HashKeyType::serialized: default-enabled 
PartitionTopN windows, multi-column INTERSECT/EXCEPT, and recursive UNION 
DISTINCT. Their PartitionedHashMapVariants, SetDataVariants, and 
DistinctDataVariants all implement serialized keys, and ColumnVarbinary 
supplies serialization; only single-key VARBINARY was already unsupported. 
Please move the rejection to the join/group consumers that require it (or 
otherwise retain these byte-safe consumers) and add multi-key window, 
set-operation, and recursive-CTE coverage.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to