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


##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionUtil.java:
##########
@@ -385,8 +391,10 @@ public static String 
generatePartitionName(PartitionKeyDesc desc) {
         String prefix = hasNullPartitionValue(desc) ? 
PARTITION_NAME_NULL_PREFIX : PARTITION_NAME_PREFIX;
         String partitionName = prefix + 
matcher.replaceAll("").replaceAll("\\,", "_");
         if (partitionName.length() > 50) {
-            partitionName = partitionName.substring(0, 30) + 
Math.abs(Objects.hash(partitionName))
-                    + "_" + System.currentTimeMillis();
+            // truncate and append a stable hash of the full name; no time 
suffix so repeated generation
+            // (e.g. MTMV creation vs. partition refresh validation) always 
produces the same name.
+            // Cast to long before abs so Integer.MIN_VALUE (whose abs stays 
negative) cannot slip through.
+            partitionName = partitionName.substring(0, 30) + Math.abs((long) 
Objects.hash(partitionName));

Review Comment:
   [P1] Make the deterministic partition identity collision-safe
   
   The suffix has only 32 bits. Two valid long values with the same first 30 
sanitized characters and the standard Java `Aa`/`BB` hash collision generate 
the same physical name. Initial MTMV creation then fails with a duplicate 
partition name; if the second base partition appears later, the `IF NOT EXISTS` 
add silently no-ops and leaves it permanently unrepresented. Please use a 
collision-resistant identity and explicitly reject a 
same-name/different-description add.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -1340,7 +1340,13 @@ public void checkQuerySlotCount(String slotCnt) {
     public int netReadTimeout = 600;
 
     // The current time zone
-    @VarAttrDef.VarAttr(name = TIME_ZONE, needForward = true, 
affectQueryResultInExecution = true)
+    // affectQueryResultInPlan is required: TIMESTAMPTZ expressions 
(date_trunc/cast/floor on timestamptz)
+    // are evaluated in the session time zone, so the time zone must be 
captured when persisting session
+    // variables for views / materialized views / generated columns, and must 
be compared when deciding
+    // whether a materialized view can be used for rewrite. Otherwise a MV 
built in one time zone may be

Review Comment:
   [P1] Fence both metadata upgrade directions
   
   Pre-change objects have no `time_zone` key: a new FE overlays the old map 
onto a fresh system-default session for background refresh, and old empty maps 
are treated as unconditional matches. In the reverse direction, an old 
read-serving FE accepts new metadata but neither registers this key nor detects 
TIMESTAMPTZ functions; its nominal mismatch cache stays unguarded and can 
rewrite across zones. Please introduce an explicit compatibility/migration 
fence (or require rebuild/recreation) and test old-metadata/new-FE plus 
new-metadata/old-FE operation.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java:
##########
@@ -104,7 +108,7 @@ public AddSessionVarGuardRewriter(Map<String, String> var) {
         @Override
         public Expression visit(Expression expr, Boolean insideGuard) {
             Expression rewritten = rewriteChildren(this, expr, Boolean.FALSE);
-            if (rewritten instanceof NeedSessionVarGuard && 
!Boolean.TRUE.equals(insideGuard)) {
+            if (needsSessionVarGuard(rewritten) && 
!Boolean.TRUE.equals(insideGuard)) {

Review Comment:
   [P1] Apply the guard rewriter outside aliases
   
   `rewritePlanTree` reaches Filter, Join, Aggregate, and TopN expressions, but 
its executor's only rule matches `Alias`. A plan such as `Project(id AS id) -> 
Filter(date_trunc(ts, 'day') = ...) -> Scan` therefore leaves the predicate 
unchanged when the projected alias is unrelated. An MV built in UTC can remain 
structurally eligible in +08 even though rows around midnight differ. Please 
apply the visitor to every expression owned by these plan nodes, preserving 
named-output identity.



##########
regression-test/suites/mtmv_p0/test_timestamptz_sync_mv_rewrite_timezone.groovy:
##########
@@ -0,0 +1,111 @@
+// 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.
+
+import org.junit.Assert;
+
+/**
+ * A synchronous materialized view that materializes a time-zone sensitive 
TIMESTAMPTZ expression
+ * (e.g. date_trunc on a timestamptz column) is only valid in the session time 
zone it was built in.
+ * The query optimizer must not rewrite a query to such an MV when the query 
session time zone differs
+ * from the MV creation session time zone, otherwise the query returns the 
stale materialized value
+ * instead of the query-session semantics.
+ */
+suite("test_timestamptz_sync_mv_rewrite_timezone","mtmv") {
+    def tableName = "timestamptz_sync_mv_rewrite_timezone_table"
+    def mvName = "timestamptz_sync_mv_rewrite_timezone_mv"
+
+    sql "SET enable_nereids_planner = true"
+    sql "SET enable_fallback_to_original_planner = false"
+
+    // A sync MV is an index on the base table: dropping the table drops the 
MV too. We must NOT run
+    // `DROP MATERIALIZED VIEW ... ON <table>` here because that statement 
requires the table to exist,
+    // which it may not in a fresh test database.
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    // Build the base table and the sync MV in a UTC session.
+    sql "SET time_zone = '+00:00'"
+    sql """
+        CREATE TABLE ${tableName} (
+            id INT,
+            ts TIMESTAMPTZ(6),
+            v INT
+        )
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES('replication_num' = '1')
+    """
+    sql """
+        INSERT INTO ${tableName} VALUES
+        (1, '2024-01-01 00:30:00+00:00', 10)
+    """
+    sql "sync"
+
+    create_sync_mv(context.dbName, tableName, mvName, """
+        SELECT date_trunc(ts, 'day') AS day_ts, SUM(v) AS day_sum
+        FROM ${tableName}
+        WHERE ts IS NOT NULL
+        GROUP BY date_trunc(ts, 'day')
+    """)
+
+    // Query in a different (+08:00) session.
+    sql "SET time_zone = '+08:00'"
+
+    // Without rewrite, the query computes date_trunc in the query session 
time zone.
+    sql "SET enable_materialized_view_rewrite=false"
+    def resRewriteOff = sql """
+        SELECT CAST(date_trunc(ts, 'day') AS STRING) AS day_ts, SUM(v)
+        FROM ${tableName}
+        WHERE ts IS NOT NULL
+        GROUP BY date_trunc(ts, 'day')
+    """
+    // With rewrite enabled, the result must be identical; the UTC-built MV 
must not be used.
+    sql "SET enable_materialized_view_rewrite=true"
+    def resRewriteOn = sql """
+        SELECT CAST(date_trunc(ts, 'day') AS STRING) AS day_ts, SUM(v)
+        FROM ${tableName}
+        WHERE ts IS NOT NULL
+        GROUP BY date_trunc(ts, 'day')
+    """
+    Assert.assertEquals(resRewriteOff, resRewriteOn)
+    Assert.assertEquals(1, resRewriteOn.size())
+    Assert.assertTrue("expected 2024-01-01 00:00:00.000000+08:00, got " + 
resRewriteOn[0][0],
+            resRewriteOn[0][0].toString().contains("2024-01-01 
00:00:00.000000+08:00"))
+    Assert.assertEquals(10, resRewriteOn[0][1])
+
+    // The MV built in a UTC session must not be chosen for a +08:00 session 
query.
+    mv_rewrite_fail("""
+        SELECT CAST(date_trunc(ts, 'day') AS STRING), SUM(v)
+        FROM ${tableName}
+        WHERE ts IS NOT NULL
+        GROUP BY date_trunc(ts, 'day')
+    """, mvName)
+
+    // In the SAME (+08:00) session, an MV built in this session rewrites 
correctly and keeps results equal.
+    create_sync_mv(context.dbName, tableName, mvName, """
+        SELECT date_trunc(ts, 'day') AS day_ts, SUM(v) AS day_sum
+        FROM ${tableName}
+        WHERE ts IS NOT NULL
+        GROUP BY date_trunc(ts, 'day')
+    """)
+    def resSameTz = sql """

Review Comment:
   [P2] Assert that the same-zone MV is actually selected
   
   After recreating the MV in +08, this only compares query results. A 
base-table scan produces the same rows, so the suite still passes if 
TIMESTAMPTZ MV rewrite is disabled in every session. Please add 
`mv_rewrite_success` (or an equivalent plan assertion) for this positive 
same-zone case and retain the result check.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java:
##########
@@ -121,6 +125,30 @@ public Expression 
visitSessionVarGuardExpr(SessionVarGuardExpr expr, Boolean con
             }
             return expr;
         }
+
+        /**
+         * An expression needs a session variable guard when either
+         * 1. it implements {@link NeedSessionVarGuard} (its value depends on 
some session variable), or
+         * 2. it is time-zone sensitive: it operates on a TIMESTAMPTZ value. 
TIMESTAMPTZ is stored as UTC and
+         *    any expression that transforms a TIMESTAMPTZ operand (e.g. 
date_trunc, cast to varchar/datetime,
+         *    floor functions) yields a value that depends on the session time 
zone. Without a guard, a
+         *    materialized view built in one time zone could be rewritten in a 
session with a different time
+         *    zone, returning stale materialized values.
+         */
+        private static boolean needsSessionVarGuard(Expression expr) {
+            return expr instanceof NeedSessionVarGuard || 
isTimeZoneSensitive(expr);
+        }
+
+        private static boolean isTimeZoneSensitive(Expression expr) {
+            if (expr instanceof Slot || expr instanceof Literal) {
+                return false;
+            }
+            try {
+                return expr.anyMatch(e -> ((Expression) e).getDataType() 
instanceof TimeStampTzType);

Review Comment:
   [P1] Classify the actual operation and nested type dependency
   
   This top-level descendant test is wrong in both directions. It guards 
zone-invariant scalar operations such as `COUNT(ts)`, `MIN/MAX(ts)`, and `ts IS 
NULL`, disabling safe rewrites and rescanning nested trees repeatedly. 
Conversely, no node in `array_join(array_sort(arr), '|')` over 
`ARRAY<TIMESTAMPTZ>` has top-level `TimeStampTzType`, so the zone-dependent 
string conversion gets no guard at all; existing array output shows nested 
values rendered in the session zone. A UTC-materialized string can therefore 
rewrite in +08 and return the stored UTC rendering. Please model the operations 
that actually depend on timezone, including nested complex-type conversions.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/RefreshMTMVInfo.java:
##########
@@ -105,7 +112,8 @@ private void checkPartitionExist(MTMV mtmv) throws 
org.apache.doris.common.Analy
                 shouldExistPartitionNames.add(((SinglePartitionDesc) 
desc).getPartitionName());
             });
             for (String partition : partitions) {
-                if (!shouldExistPartitionNames.contains(partition)) {
+                if (!existPartitionNames.contains(partition)

Review Comment:
   [P1] Revalidate manual partitions after alignment
   
   Accepting any stored physical name also admits a stale one. If its base 
partition was dropped, or changes between analysis and the async task, 
`alignMvPartition` removes the MV partition but the manual request keeps the 
old name; the rebuilt mapping returns null and snapshot generation dereferences 
it. Please require the physical name's descriptor to remain current and 
revalidate the manual set after alignment before constructing snapshots or the 
overwrite sink.



##########
regression-test/suites/mtmv_p0/test_timestamptz_partition_mtmv_refresh_timezone.groovy:
##########
@@ -0,0 +1,110 @@
+// 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.
+
+import org.junit.Assert;
+
+/**
+ * When an asynchronous partitioned materialized view is created in a session 
with a non-default time zone
+ * and its partition key is a time-zone sensitive expression (date_trunc on a 
TIMESTAMPTZ column), the
+ * background refresh must run with the SAME session time zone that was used 
to derive the MV partition
+ * boundaries. Otherwise the refresh computes a partition key that does not 
fall into any MV partition
+ * ("no partition for this tuple") and the MV stays empty.
+ */
+suite("test_timestamptz_partition_mtmv_refresh_timezone","mtmv") {
+    def dbName = "timestamptz_partition_mtmv_refresh_timezone"
+    def tableName = "timestamptz_partition_mtmv_refresh_timezone_table"
+    def mvName = "timestamptz_partition_mtmv_refresh_timezone_mv"
+
+    sql "DROP DATABASE IF EXISTS ${dbName}"
+    sql "CREATE DATABASE ${dbName}"
+    sql "USE ${dbName}"
+
+    sql "SET enable_nereids_planner = true"
+    sql "SET enable_fallback_to_original_planner = false"
+    sql "SET time_zone = '+00:00'"

Review Comment:
   [P2] Use a creation zone different from the FE default
   
   This suite describes a non-default creation zone but selects `+00:00`; the 
regression runner and JVM default are UTC/Etc/UTC. Before this change, the 
fresh background context therefore evaluates under the same effective zone and 
can pass without restoring any persisted `time_zone`. Please choose a zone 
proven different from the FE default and keep a boundary row that makes the 
pre-fix refresh deterministically fail or materialize the wrong day.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -1340,7 +1340,13 @@ public void checkQuerySlotCount(String slotCnt) {
     public int netReadTimeout = 600;
 
     // The current time zone
-    @VarAttrDef.VarAttr(name = TIME_ZONE, needForward = true, 
affectQueryResultInExecution = true)
+    // affectQueryResultInPlan is required: TIMESTAMPTZ expressions 
(date_trunc/cast/floor on timestamptz)
+    // are evaluated in the session time zone, so the time zone must be 
captured when persisting session
+    // variables for views / materialized views / generated columns, and must 
be compared when deciding
+    // whether a materialized view can be used for rewrite. Otherwise a MV 
built in one time zone may be
+    // rewritten in a session with a different time zone and return stale 
materialized values.
+    @VarAttrDef.VarAttr(name = TIME_ZONE, needForward = true, 
affectQueryResultInPlan = true,

Review Comment:
   [P2] Scope mismatches to the session dependency that changed
   
   The complete maps are compared as raw strings and reduced to one mismatch 
Boolean. This treats equivalent spellings such as `UTC`, `Etc/UTC`, `GMT`, and 
`+00:00` as different. A genuinely different but expression-irrelevant zone 
also activates every older `NeedSessionVarGuard`; for example, integer `SUM` 
has no TIMESTAMPTZ dependency but loses rewrite across zones. Please 
canonicalize timezone identity and propagate per-variable dependency 
differences instead of enabling all guard families from any map mismatch.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java:
##########
@@ -1340,7 +1340,13 @@ public void checkQuerySlotCount(String slotCnt) {
     public int netReadTimeout = 600;
 
     // The current time zone
-    @VarAttrDef.VarAttr(name = TIME_ZONE, needForward = true, 
affectQueryResultInExecution = true)
+    // affectQueryResultInPlan is required: TIMESTAMPTZ expressions 
(date_trunc/cast/floor on timestamptz)
+    // are evaluated in the session time zone, so the time zone must be 
captured when persisting session
+    // variables for views / materialized views / generated columns, and must 
be compared when deciding

Review Comment:
   [P1] Preserve the captured zone through stored-expression execution
   
   Adding `time_zone` to the persisted map changes binding, but not runtime 
materialization. `BindSink` adds a guard around a generated/sync-MV 
`DateTrunc`; the mandatory final `MergeGuardExpr` removes it because 
`DateTrunc` does not implement `NeedSessionVarGuard`, translation also unwraps 
guards, and BE receives the current insert session's zone. Thus an expression 
created in UTC and written in +08 materializes +08 values. Please make the 
creation-zone semantics survive execution, or reject unsupported stored 
expressions, and cover post-creation cross-zone writes.



-- 
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