This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new bb6be98ffa6 [fix](fe) Send canonical time zone IDs to backends and 
connectors (#68865)
bb6be98ffa6 is described below

commit bb6be98ffa691532a704600cd2fde8d9c1fbb2f7
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Oct 12 13:31:45 2026 +0800

    [fix](fe) Send canonical time zone IDs to backends and connectors (#68865)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #65868
    
    Problem Summary:
    
    **In short.** This ports the time-zone part of branch-4.1 #65868.
    `time_zone` accepts Java short IDs such as JST, PST or IST, but the FE
    forwards them as written, except for CST. Backends do not know these
    names: they silently evaluate time functions in another zone, and Java
    code running on a backend fails on them. The FE now sends the canonical
    zone ID wherever a time zone leaves the session layer: to backends for
    queries, loads, BE constant folding and point queries, and to
    connectors.
    
    **Background.** `SET time_zone` validates the name against
    `TimeUtils.timeZoneAliasMap`, which is `ZoneId.SHORT_IDS` plus the Doris
    overrides (CST and PRC map to Asia/Shanghai, UTC and GMT to UTC), and
    stores the name as written. When the FE evaluates time itself, it
    resolves the name through that map. The places that hand the time zone
    on behave differently:
    
    | Where the time zone goes | master before this PR |
    |---|---|
    | Query globals (`CoordinatorContext`), BE constant folding,
    short-circuit point queries | raw name, CST special-cased to
    Asia/Shanghai |
    | Broker load and export (`setQueryGlobalsForLoad`), stream and routine
    load (`NereidsStreamLoadPlanner`) | raw name |
    | Connectors (`ConnectorSessionBuilder`) | raw name |
    
    branch-4.1 #65868 added `TimeUtils.getCanonicalTimeZone`
    (`ZoneId.of(name, timeZoneAliasMap).getId()`) and used it for the query,
    constant-folding and point-query paths. master does not have it.
    
    **The problem, and what it cost.**
    
    - **Wrong results, no error.** A backend looks the name up in its tz
    database, which has only EST, HST and MST of the 28 Java short IDs. For
    the others (JST, PST, IST, CTT and 20 more; CST is special-cased),
    `RuntimeState` ignores the failed lookup and keeps UTC, and a
    short-circuit point query falls back to the backend's default zone.
    Constant expressions folded on the FE are still right, so one query can
    mix two zones.
    - **Loads convert in the wrong zone.** A stream load or broker load
    whose `timezone` is such a name converts with the same fallback.
    - **Java code on the backend fails.** It resolves the name with
    `ZoneId.of`, which rejects short IDs. A Paimon INSERT under JST fails
    with `ZoneRulesException: Unknown time-zone ID: JST`.
    - **Connectors get the raw name.** The Iceberg and Paimon connectors
    carry their own copies of the alias map. The MaxCompute predicate
    converter does not, so for a session in CST it stops pushing down
    datetime predicates.
    
    **How this PR fixes it.**
    
    - Adds `TimeUtils.getCanonicalTimeZone`, as in branch-4.1.
    - Applies it in `CoordinatorContext.refreshQueryGlobals` (queries, group
    commit, jobs with a session), `createQueryGlobalsWithoutSession` (jobs
    started by FE daemons), `setQueryGlobalsForLoad` (broker load, export),
    `NereidsStreamLoadPlanner` (stream and routine load, transactional
    INSERT), `FoldConstantRuleOnBE`, `PointQueryExecutor` and
    `ConnectorSessionBuilder`. The CST special cases go away, because the
    alias map already resolves CST to Asia/Shanghai.
    - Names a backend already understands are unchanged (`Asia/Tokyo`,
    `UTC`, `+08:00`, `GMT+08:00`). Short IDs become tz database names or
    offsets: JST becomes `Asia/Tokyo`, PST `America/Los_Angeles`, EST
    `-05:00`.
    - A zone that is UTC at every instant goes out as `UTC`. Java names
    `+00:00` `Z` and `GMT+00:00` a bare `GMT`, which a backend resolves only
    from the optional `Zulu` and `GMT` zone files (Debian 13's `tzdata` has
    no `Zulu`); `UTC` needs no zone file.
    - The other time zones a backend receives need no change.
    `hive.parquet.time-zone` rejects short IDs when the catalog or TVF is
    created. The zones a backend hands on, to Java scanners and writers,
    Arrow Flight results and partial updates, come from the query globals.
    
    **Results.** Local FE + BE, a row with `ts` = 0, `time_zone` = JST:
    
    | | before | after |
    |---|---|---|
    | `select from_unixtime(ts) from t` | 1970-01-01 00:00:00 | 1970-01-01
    09:00:00 |
    | the same query as a short-circuit point query | 1970-01-01 08:00:00 |
    1970-01-01 09:00:00 |
    | stream load with `timezone: JST` and `dt = from_unixtime(ts)` |
    1970-01-01 00:00:00 | 1970-01-01 09:00:00 |
    | INSERT into a filesystem Paimon table | fails: `Unknown time-zone ID:
    JST` | succeeds |
    
    PST, IST and CTT behaved like JST before the fix. EST and CST were
    already right.
    Co-authored-by: Socrates <[email protected]>
---
 .../maxcompute/MaxComputeScanPlanProvider.java     | 12 ++--
 .../org/apache/doris/common/util/TimeUtils.java    |  9 +++
 .../doris/connector/ConnectorSessionBuilder.java   |  3 +-
 .../nereids/load/NereidsStreamLoadPlanner.java     |  3 +-
 .../expression/rules/FoldConstantRuleOnBE.java     |  8 +--
 .../org/apache/doris/qe/CoordinatorContext.java    | 12 ++--
 .../org/apache/doris/qe/PointQueryExecutor.java    |  8 +--
 .../apache/doris/common/util/TimeUtilsTest.java    | 26 ++++++++
 .../doris/connector/ConnectorSessionImplTest.java  | 12 ++++
 .../apache/doris/qe/CoordinatorContextTest.java    | 20 +++++++
 .../test_session_time_zone_short_ids.out           | 31 ++++++++++
 .../write/test_paimon_write_append_only.groovy     | 11 ++++
 .../test_session_time_zone_short_ids.groovy        | 69 ++++++++++++++++++++++
 13 files changed, 197 insertions(+), 27 deletions(-)

diff --git 
a/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputeScanPlanProvider.java
 
b/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputeScanPlanProvider.java
index e28bbde6bb7..0c401ee7874 100644
--- 
a/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputeScanPlanProvider.java
+++ 
b/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputeScanPlanProvider.java
@@ -262,12 +262,12 @@ public class MaxComputeScanPlanProvider implements 
ConnectorScanPlanProvider {
 
         // Source time zone = the session time zone, mirroring legacy
         // MaxComputeScanNode.convertDateTimezone's DateUtils.getTimeZone() (= 
the session var).
-        // ConnectorSession.getTimeZone() is populated from 
ctx.getSessionVariable().getTimeZone()
-        // by ConnectorSessionBuilder.from(ctx), so this is the same source as 
legacy. (The earlier
-        // project-region TZ from the endpoint was wrong: Doris interprets 
datetime literals in the
-        // session TZ, so converting from any other zone shifts the 
pushed-down UTC literal.) The id
-        // is passed raw and parsed lazily inside the converter, so a 
Doris-valid-but-ZoneId-invalid
-        // value (e.g. "CST") degrades the datetime predicate instead of 
failing the query.
+        // ConnectorSessionBuilder.from(ctx) fills 
ConnectorSession.getTimeZone() with the canonical
+        // id of ctx.getSessionVariable().getTimeZone() (CST becomes 
Asia/Shanghai, as legacy resolved
+        // it), so this is the same zone as legacy. (The earlier 
project-region TZ from the endpoint
+        // was wrong: Doris interprets datetime literals in the session TZ, so 
converting from any
+        // other zone shifts the pushed-down UTC literal.) The converter 
parses the id lazily, so an
+        // id ZoneId.of rejects degrades the datetime predicate instead of 
failing the query.
         MaxComputePredicateConverter converter = new 
MaxComputePredicateConverter(
                 columnTypeMap, dateTimePushDown, session.getTimeZone());
         return converter.convert(filter.get());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/util/TimeUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/TimeUtils.java
index f5e15f67981..bd2bcabc2d8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/TimeUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/TimeUtils.java
@@ -39,6 +39,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoField;
@@ -142,6 +143,14 @@ public class TimeUtils {
         return getTimeZone().toZoneId();
     }
 
+    /** Resolve a Doris time-zone name to the canonical ID understood by 
execution backends. */
+    public static String getCanonicalTimeZone(String timeZone) {
+        ZoneId zoneId = ZoneId.of(timeZone, timeZoneAliasMap);
+        // Java names a zero offset Z (+00:00) or a bare GMT (GMT+00:00). A 
backend finds those names only in a
+        // zone directory that ships the optional Zulu or GMT file, and it 
resolves UTC without one.
+        return zoneId.normalized().equals(ZoneOffset.UTC) ? UTC_TIME_ZONE : 
zoneId.getId();
+    }
+
     public static TimeZone getUTCTimeZone() {
         return TimeZone.getTimeZone(UTC_TIME_ZONE);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/connector/ConnectorSessionBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/connector/ConnectorSessionBuilder.java
index e9cf180dc2d..bdaeae2f4e9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/connector/ConnectorSessionBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/connector/ConnectorSessionBuilder.java
@@ -19,6 +19,7 @@ package org.apache.doris.connector;
 
 import org.apache.doris.common.Config;
 import org.apache.doris.common.util.DebugUtil;
+import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.connector.spi.ConnectorDelegatedCredential;
 import org.apache.doris.connector.spi.ConnectorSession;
 import org.apache.doris.connector.spi.ConnectorStatementScope;
@@ -74,7 +75,7 @@ public final class ConnectorSessionBuilder {
         ConnectorSessionBuilder b = new ConnectorSessionBuilder();
         b.queryId = ctx.queryId() != null ? DebugUtil.printId(ctx.queryId()) : 
"";
         b.user = ctx.getQualifiedUser();
-        b.timeZone = ctx.getSessionVariable().getTimeZone();
+        b.timeZone = 
TimeUtils.getCanonicalTimeZone(ctx.getSessionVariable().getTimeZone());
         b.locale = "en_US";  // Doris doesn't have per-session locale yet
         b.sessionProperties = extractSessionProperties(ctx);
         b.connectContext = ctx;  // read for the delegated credential at 
build() time, gated by capability
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsStreamLoadPlanner.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsStreamLoadPlanner.java
index 78896eae5aa..e3be4cbdfb9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsStreamLoadPlanner.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsStreamLoadPlanner.java
@@ -30,6 +30,7 @@ import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.load.loadv2.LoadTask;
 import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -326,7 +327,7 @@ public class NereidsStreamLoadPlanner {
         params.setQueryOptions(queryOptions);
         TQueryGlobals queryGlobals = new TQueryGlobals();
         CoordinatorContext.setQueryGlobalsCurrentTime(queryGlobals);
-        queryGlobals.setTimeZone(taskInfo.getTimezone());
+        
queryGlobals.setTimeZone(TimeUtils.getCanonicalTimeZone(taskInfo.getTimezone()));
         if (taskInfo instanceof NereidsRoutineLoadTaskInfo) {
             queryGlobals.setLoadZeroTolerance(false);
         } else {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnBE.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnBE.java
index ff575c05e7f..2c0789e01c4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnBE.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnBE.java
@@ -340,12 +340,8 @@ public class FoldConstantRuleOnBE implements 
ExpressionPatternRuleFactory {
             TQueryGlobals queryGlobals = new TQueryGlobals();
             
queryGlobals.setNowString(TimeUtils.getDatetimeFormatWithTimeZone().format(LocalDateTime.now()));
             queryGlobals.setTimestampMs(System.currentTimeMillis());
-            queryGlobals.setTimeZone(TimeUtils.DEFAULT_TIME_ZONE);
-            if (context.getSessionVariable().getTimeZone().equals("CST")) {
-                queryGlobals.setTimeZone(TimeUtils.DEFAULT_TIME_ZONE);
-            } else {
-                
queryGlobals.setTimeZone(context.getSessionVariable().getTimeZone());
-            }
+            queryGlobals.setTimeZone(
+                    
TimeUtils.getCanonicalTimeZone(context.getSessionVariable().getTimeZone()));
 
             TQueryOptions tQueryOptions = new TQueryOptions();
             tQueryOptions.setBeExecVersion(Config.be_exec_version);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/qe/CoordinatorContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/CoordinatorContext.java
index 6951a49d456..9e21d760f7c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/CoordinatorContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/CoordinatorContext.java
@@ -358,8 +358,8 @@ public class CoordinatorContext {
     public static TQueryGlobals createQueryGlobalsWithoutSession() {
         TQueryGlobals queryGlobals = new TQueryGlobals();
         setQueryGlobalsCurrentTime(queryGlobals);
-        String timeZone = 
VariableMgr.getDefaultSessionVariable().getTimeZone();
-        queryGlobals.setTimeZone(timeZone.equals("CST") ? 
TimeUtils.DEFAULT_TIME_ZONE : timeZone);
+        queryGlobals.setTimeZone(
+                
TimeUtils.getCanonicalTimeZone(VariableMgr.getDefaultSessionVariable().getTimeZone()));
         queryGlobals.setLoadZeroTolerance(false);
         return queryGlobals;
     }
@@ -371,7 +371,7 @@ public class CoordinatorContext {
     public static void setQueryGlobalsForLoad(
             TQueryGlobals queryGlobals, String timezone, boolean 
loadZeroTolerance) {
         setQueryGlobalsCurrentTime(queryGlobals);
-        queryGlobals.setTimeZone(timezone);
+        queryGlobals.setTimeZone(TimeUtils.getCanonicalTimeZone(timezone));
         queryGlobals.setLoadZeroTolerance(loadZeroTolerance);
     }
 
@@ -383,11 +383,7 @@ public class CoordinatorContext {
 
     public static void refreshQueryGlobals(TQueryGlobals queryGlobals, 
ConnectContext context) {
         setQueryGlobalsCurrentTime(queryGlobals, 
context.getStartTimeInstant());
-        if (context.getSessionVariable().getTimeZone().equals("CST")) {
-            queryGlobals.setTimeZone(TimeUtils.DEFAULT_TIME_ZONE);
-        } else {
-            
queryGlobals.setTimeZone(context.getSessionVariable().getTimeZone());
-        }
+        
queryGlobals.setTimeZone(TimeUtils.getCanonicalTimeZone(context.getSessionVariable().getTimeZone()));
         
queryGlobals.setLcTimeNames(context.getSessionVariable().getLcTimeNames());
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExecutor.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExecutor.java
index f61c7ffae09..0b51c6bcb10 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExecutor.java
@@ -33,6 +33,7 @@ import org.apache.doris.cloud.catalog.CloudPartition;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.Status;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.mysql.MysqlCommand;
 import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.exceptions.AnalysisException;
@@ -425,11 +426,8 @@ public class PointQueryExecutor implements CoordInterface {
         }
 
         // Set timezone for functions like from_unixtime
-        String timeZone = 
ConnectContext.get().getSessionVariable().getTimeZone();
-        if ("CST".equals(timeZone)) {
-            timeZone = "Asia/Shanghai";
-        }
-        requestBuilder.setTimeZone(timeZone);
+        requestBuilder.setTimeZone(TimeUtils.getCanonicalTimeZone(
+                ConnectContext.get().getSessionVariable().getTimeZone()));
 
         if (snapshotVisibleVersions != null && 
!snapshotVisibleVersions.isEmpty()) {
             requestBuilder.setVersion(snapshotVisibleVersions.get(0));
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/common/util/TimeUtilsTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/common/util/TimeUtilsTest.java
index f69ab124c1f..e21950bee4f 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/common/util/TimeUtilsTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/common/util/TimeUtilsTest.java
@@ -181,6 +181,32 @@ public class TimeUtilsTest {
         }
     }
 
+    @Test
+    public void testCanonicalTimeZone() {
+        // A Java short ID is a valid time_zone, but a backend only knows tz 
database names and offsets, and the
+        // Java scanners and writers it runs resolve the name with a plain 
ZoneId.of. MUTATION: passing the name
+        // through unchanged -> the short IDs below stay as they are -> red.
+        Assertions.assertEquals("Asia/Shanghai", 
TimeUtils.getCanonicalTimeZone("CST"));
+        Assertions.assertEquals("-05:00", 
TimeUtils.getCanonicalTimeZone("EST"));
+        Assertions.assertEquals("Asia/Tokyo", 
TimeUtils.getCanonicalTimeZone("JST"));
+        Assertions.assertEquals("America/Los_Angeles", 
TimeUtils.getCanonicalTimeZone("PST"));
+        Assertions.assertEquals("UTC", TimeUtils.getCanonicalTimeZone("GMT"));
+        // Names a backend already understands pass through unchanged.
+        Assertions.assertEquals("Asia/Tokyo", 
TimeUtils.getCanonicalTimeZone("Asia/Tokyo"));
+        Assertions.assertEquals("UTC", TimeUtils.getCanonicalTimeZone("UTC"));
+        Assertions.assertEquals("+08:00", 
TimeUtils.getCanonicalTimeZone("+08:00"));
+        Assertions.assertEquals("GMT+08:00", 
TimeUtils.getCanonicalTimeZone("GMT+08:00"));
+        // Java names a zero offset Z or a bare GMT, which a backend resolves 
only from the optional Zulu or GMT
+        // zone file (Debian 13's tzdata ships no Zulu). MUTATION: returning 
ZoneId.getId() for these -> red.
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("+00:00"));
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("-00:00"));
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("GMT+00:00"));
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("GMT-00:00"));
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("UTC+00:00"));
+        Assertions.assertEquals("UTC", 
TimeUtils.getCanonicalTimeZone("Etc/UTC"));
+        Assertions.assertEquals("-00:30", 
TimeUtils.getCanonicalTimeZone("-00:30"));
+    }
+
     @Test
     public void testGetHourAsDate() {
         Calendar calendar = Calendar.getInstance();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/connector/ConnectorSessionImplTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/connector/ConnectorSessionImplTest.java
index a86bc06d5c5..eb2b2b46ce9 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/connector/ConnectorSessionImplTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/connector/ConnectorSessionImplTest.java
@@ -313,6 +313,18 @@ public class ConnectorSessionImplTest {
         Assertions.assertFalse(session.getDelegatedCredential().isPresent());
     }
 
+    @Test
+    public void sessionTimeZoneReachesConnectorsAsCanonicalId() {
+        // Connectors resolve the session time zone with ZoneId.of, which 
rejects Doris aliases such as CST or JST
+        // (MaxCompute then stops pushing down datetime predicates). MUTATION: 
copying the session value as is ->
+        // red.
+        ConnectContext ctx = new ConnectContext();
+        ctx.getSessionVariable().setTimeZone("JST");
+        Assertions.assertEquals("Asia/Tokyo", 
ConnectorSessionBuilder.from(ctx).build().getTimeZone());
+        ctx.getSessionVariable().setTimeZone("CST");
+        Assertions.assertEquals("Asia/Shanghai", 
ConnectorSessionBuilder.from(ctx).build().getTimeZone());
+    }
+
     @Test
     public void explicitNoneStatementScopeWinsOverLiveContext() {
         // Callers that deliberately request NONE must not accidentally 
inherit a live statement scope. Current
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorContextTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorContextTest.java
index 8c2ff6cb49f..9ecb6e7f68b 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorContextTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorContextTest.java
@@ -72,6 +72,26 @@ class CoordinatorContextTest {
         Assertions.assertFalse(queryGlobals.isLoadZeroTolerance());
     }
 
+    // A backend resolves the time zone against its tz database, which has no 
Java short IDs: it ran a query or a
+    // load under JST in the wrong zone, and a Java writer failed on 
ZoneId.of("JST"). MUTATION: setting the name
+    // as the session or the load gives it -> red.
+    @Test
+    void testQueryGlobalsCarryCanonicalTimeZones() {
+        ConnectContext context = new ConnectContext();
+        context.getSessionVariable().setTimeZone("JST");
+        TQueryGlobals queryGlobals = new TQueryGlobals();
+        CoordinatorContext.refreshQueryGlobals(queryGlobals, context);
+        Assertions.assertEquals("Asia/Tokyo", queryGlobals.getTimeZone());
+
+        context.getSessionVariable().setTimeZone("CST");
+        CoordinatorContext.refreshQueryGlobals(queryGlobals, context);
+        Assertions.assertEquals("Asia/Shanghai", queryGlobals.getTimeZone());
+
+        TQueryGlobals loadGlobals = new TQueryGlobals();
+        CoordinatorContext.setQueryGlobalsForLoad(loadGlobals, "PST", false);
+        Assertions.assertEquals("America/Los_Angeles", 
loadGlobals.getTimeZone());
+    }
+
     @Test
     void testLoadQueryGlobalsSetNanoseconds() {
         TQueryGlobals queryGlobals = new TQueryGlobals();
diff --git 
a/regression-test/data/query_p0/session_variable/test_session_time_zone_short_ids.out
 
b/regression-test/data/query_p0/session_variable/test_session_time_zone_short_ids.out
new file mode 100644
index 00000000000..e676fa1268d
--- /dev/null
+++ 
b/regression-test/data/query_p0/session_variable/test_session_time_zone_short_ids.out
@@ -0,0 +1,31 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !scan --
+Asia/Tokyo     1970-01-01 09:00:00
+
+-- !scan --
+JST    1970-01-01 09:00:00
+
+-- !scan --
+PST    1969-12-31 16:00:00
+
+-- !scan --
+IST    1970-01-01 05:30:00
+
+-- !scan --
+CTT    1970-01-01 08:00:00
+
+-- !scan --
+EST    1969-12-31 19:00:00
+
+-- !scan --
+CST    1970-01-01 08:00:00
+
+-- !point --
+1      1970-01-01 09:00:00
+
+-- !load_status --
+Success        1
+
+-- !load --
+1      0       1970-01-01T09:00
+
diff --git 
a/regression-test/suites/external_table_p0/paimon/write/test_paimon_write_append_only.groovy
 
b/regression-test/suites/external_table_p0/paimon/write/test_paimon_write_append_only.groovy
index d2a25feb65a..8df4522a5a2 100644
--- 
a/regression-test/suites/external_table_p0/paimon/write/test_paimon_write_append_only.groovy
+++ 
b/regression-test/suites/external_table_p0/paimon/write/test_paimon_write_append_only.groovy
@@ -70,6 +70,11 @@ suite("test_paimon_write_append_only", "p0,external,paimon") 
{
             id INT, name STRING, dt STRING NOT NULL DEFAULT '2026-07-01'
         ) USING paimon
         PARTITIONED BY (dt);
+
+        DROP TABLE IF EXISTS paimon.${dbName}.t_append_time_zone;
+        CREATE TABLE paimon.${dbName}.t_append_time_zone (
+            id INT, name STRING
+        ) USING paimon;
     """
 
     sql """drop catalog if exists ${catalogName}"""
@@ -212,6 +217,12 @@ suite("test_paimon_write_append_only", 
"p0,external,paimon") {
             sql """INSERT INTO t_append (id, ID) VALUES (8, 9)"""
             exception "Column 'ID' specified twice"
         }
+
+        // A Java short time-zone ID such as JST reaches the JNI writer as its 
canonical zone ID.
+        sql """set time_zone = 'JST'"""
+        sql """INSERT INTO t_append_time_zone VALUES (1, 'alice')"""
+        sql """set time_zone = default"""
+        assertTableEquals("t_append_time_zone", "ORDER BY id")
     } finally {
         sql """drop catalog if exists ${catalogName}"""
     }
diff --git 
a/regression-test/suites/query_p0/session_variable/test_session_time_zone_short_ids.groovy
 
b/regression-test/suites/query_p0/session_variable/test_session_time_zone_short_ids.groovy
new file mode 100644
index 00000000000..3fb54544b87
--- /dev/null
+++ 
b/regression-test/suites/query_p0/session_variable/test_session_time_zone_short_ids.groovy
@@ -0,0 +1,69 @@
+// 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.
+
+
+// time_zone accepts Java short IDs such as JST or PST, which the backend tz 
database does not have. The frontend
+// must hand backends the canonical zone ID; otherwise a backend computes in a 
fallback zone without any error.
+suite("test_session_time_zone_short_ids") {
+    sql "drop table if exists test_session_time_zone_short_ids"
+    sql """
+        create table test_session_time_zone_short_ids (k int, ts bigint)
+        unique key(k) distributed by hash(k) buckets 1
+        properties ("replication_num" = "1", 
"enable_unique_key_merge_on_write" = "true",
+                "store_row_column" = "true")
+    """
+    sql "insert into test_session_time_zone_short_ids values (1, 0)"
+
+    // from_unixtime runs on the backend, in the time zone of the query.
+    for (String tz : ["Asia/Tokyo", "JST", "PST", "IST", "CTT", "EST", "CST"]) 
{
+        sql "set time_zone = '${tz}'"
+        order_qt_scan "select '${tz}', from_unixtime(ts) from 
test_session_time_zone_short_ids"
+    }
+
+    // A short-circuit point query sends the time zone in its own request.
+    sql "set time_zone = 'JST'"
+    sql "set enable_short_circuit_query = true"
+    explain {
+        sql "select k, from_unixtime(ts) from test_session_time_zone_short_ids 
where k = 1"
+        contains "SHORT-CIRCUIT"
+    }
+    order_qt_point "select k, from_unixtime(ts) from 
test_session_time_zone_short_ids where k = 1"
+    sql "set time_zone = default"
+
+    // A stream load carries its own time zone.
+    sql "drop table if exists test_session_time_zone_short_ids_load"
+    sql """
+        create table test_session_time_zone_short_ids_load (k int, ts bigint, 
dt datetime)
+        duplicate key(k) distributed by hash(k) buckets 1
+        properties ("replication_num" = "1")
+    """
+    streamLoad {
+        table "test_session_time_zone_short_ids_load"
+        set "column_separator", ","
+        set "timezone", "JST"
+        set "columns", "k, ts, dt = from_unixtime(ts)"
+        inputText "1,0\n"
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
+            }
+            def json = parseJson(result)
+            qt_load_status "select '${json.Status}', ${json.NumberLoadedRows}"
+        }
+    }
+    order_qt_load "select k, ts, dt from test_session_time_zone_short_ids_load"
+}


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

Reply via email to