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


##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVCacheManager.java:
##########
@@ -46,21 +46,21 @@ public MTMVCacheManager() {
         caches = build(Config.mtmv_cache_manage_num, 
Config.expire_mtmv_cache_in_fe_second);
     }
 
-    public MTMVCache getIfPresent(long mtmvId, boolean guarded) {
-        return caches.getIfPresent(new Key(mtmvId, guarded));
+    public MTMVCache getIfPresent(long mtmvId, int guardMask) {
+        return caches.getIfPresent(new Key(mtmvId, guardMask));
     }
 
-    public void put(long mtmvId, boolean guarded, MTMVCache cache) {
+    public void put(long mtmvId, int guardMask, MTMVCache cache) {
         Objects.requireNonNull(cache, "mtmv cache to publish must not be 
null");
         synchronized (swapLock) {
-            caches.put(new Key(mtmvId, guarded), cache);
+            caches.put(new Key(mtmvId, guardMask), cache);
         }
     }
 
     public void invalidate(long mtmvId) {
         synchronized (swapLock) {
-            caches.invalidate(new Key(mtmvId, true));
-            caches.invalidate(new Key(mtmvId, false));
+            // Every guard mask is an independent entry for the same MV, so 
drop them all.
+            caches.asMap().keySet().removeIf(key -> key.mtmvId == mtmvId);

Review Comment:
   [P2] Invalidate the four mask keys without scanning the shared cache
   
   Every successful MTMV refresh calls this through `invalidateRewriteCache()` 
while holding the MV write lock, and `swapLock` also serializes all cache puts 
and config swaps. The new `removeIf` traverses every cached MV to remove at 
most four entries. With N cached one-entry MVs, refreshing the fleet now 
performs O(N²) key visits while unrelated publications wait; the default 
capacity is 3000 and is configurable without an upper bound. Both production 
writers only create masks 0, 1, 2 or 3, so directly invalidating those four 
keys preserves the new semantics with constant per-MV work. This is separate 
from the already-reported unreachable mask-0 prebuild cost.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java:
##########
@@ -139,15 +348,268 @@ public Plan visit(Plan plan, Void ctx) {
 
     /**
      * Check if current query session variables match MV creation session 
variables.
-     * Only compares variables that affect query results.
+     * Only compares variables that affect query results. The time zone is 
compared with its canonical
+     * identity (UTC / Etc/UTC / GMT / +00:00 are the same zone) so equivalent 
spellings do not cause a
+     * mismatch and a resulting unnecessary rewrite loss.
      */
     public static boolean checkSessionVariablesMatch(Map<String, String> 
currentSessionVars,
             Map<String, String> persistSessionVars) {
         if (persistSessionVars == null || persistSessionVars.isEmpty()) {
             // If no session variables saved, consider them matched
             return true;
         }
-        return currentSessionVars.equals(persistSessionVars);
+        for (Map.Entry<String, String> entry : persistSessionVars.entrySet()) {
+            String key = entry.getKey();
+            String persistedValue = entry.getValue();
+            String currentValue = currentSessionVars.get(key);
+            if (SessionVariable.TIME_ZONE.equals(key)) {
+                if (!timeZonesEquivalent(persistedValue, currentValue)) {
+                    return false;
+                }
+            } else if (!Objects.equals(persistedValue, currentValue)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Whether the guard rewriter must be applied to the object owning {@code 
persistSessionVars}: either
+     * some persisted affectQueryResult variable no longer matches the current 
session, or the persisted
+     * map does not carry {@code time_zone} at all. The latter covers 
pre-change metadata whose creation
+     * time zone is unknown, so time-zone sensitive expressions of such 
objects must always be guarded
+     * (a conservative compatibility fence) to avoid cross-zone rewrite of 
stale materialized values.
+     */
+    public static boolean needsSessionVarGuard(Map<String, String> 
currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        return computeGuardMask(currentSessionVars, persistSessionVars) != 
GUARD_NONE;
+    }
+
+    /**
+     * Whether the persisted session-variable map identifies the creation time 
zone.
+     *
+     * <p>A missing, empty or time_zone-less map means the creation zone is 
unknown: the object was created
+     * before time_zone was persisted (old FE), or its metadata was replayed 
by a new FE without the key.
+     * Such an object is the old-metadata -> new-FE upgrade direction of the 
metadata compatibility fence:
+     * its zone-sensitive expressions are guarded forever and it must be 
recreated on the new FE before it
+     * can participate in a cross-zone rewrite. The reverse direction (new 
metadata read by an old FE)
+     * cannot be repaired from the new code: an old read-serving FE neither 
registers this key nor
+     * classifies TIMESTAMPTZ functions, so it keeps building its 
nominal-mismatch cache without the time
+     * zone guard. New-metadata objects therefore have to be rebuilt/recreated 
before an old FE serves
+     * their reads, or the old FE must not be used for reads until it is 
upgraded.
+     */
+    public static boolean isCreationSessionKnown(Map<String, String> 
persistSessionVars) {
+        return persistSessionVars != null && !persistSessionVars.isEmpty()
+                && persistSessionVars.containsKey(SessionVariable.TIME_ZONE);
+    }
+
+    /**
+     * Computes which session-variable dependency families differ between the 
query session and the
+     * persisted (creation) session, as a bitmask of {@link #GUARD_TIME_ZONE} 
and {@link #GUARD_OTHER}.
+     * Per-family scoping lets a time-zone-only difference keep rewriting 
expressions that depend on other
+     * variables (and vice versa).
+     *
+     * <p>An unknown creation session (see {@link #isCreationSessionKnown}) 
always selects the time-zone
+     * family, never an unconditional match.
+     */
+    public static int computeGuardMask(Map<String, String> currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        int mask = GUARD_NONE;
+        // The creation zone is unknown (pre-change metadata, including an 
empty map) or differs from the
+        // current zone.
+        if (!isCreationSessionKnown(persistSessionVars)
+                || 
!timeZonesEquivalent(persistSessionVars.get(SessionVariable.TIME_ZONE),
+                        currentSessionVars == null ? null : 
currentSessionVars.get(SessionVariable.TIME_ZONE))) {
+            mask |= GUARD_TIME_ZONE;
+        }
+        if (persistSessionVars != null && !persistSessionVars.isEmpty()
+                && differsInNonTimeZoneVars(currentSessionVars, 
persistSessionVars)) {
+            mask |= GUARD_OTHER;
+        }
+        return mask;
+    }
+
+    /**
+     * Guard mask for the rewrite cache of a synchronous materialized view.
+     *
+     * <p>Sync MVs keep one deliberate exception: a difference confined to the 
strict-table sql_mode bits
+     * does not change the semantics of the rewritten expressions, so it must 
not disable rewrite (see
+     * {@link #checkSessionVariablesMatchForSyncMv}). The exception is only 
consulted when the mask selects
+     * the non-time-zone family on its own, and it can therefore never 
suppress the time-zone fence: a
+     * legacy sync MV whose persisted map is empty or time_zone-less is still 
guarded for the time zone
+     * (the full mask is returned), so an empty map is never an unconditional 
match and the cache cannot be
+     * rewritten in a different zone.
+     */
+    public static int computeSyncMvGuardMask(Map<String, String> 
currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        int guardMask = computeGuardMask(currentSessionVars, 
persistSessionVars);
+        if (guardMask == GUARD_OTHER
+                && checkSessionVariablesMatchForSyncMv(currentSessionVars, 
persistSessionVars)) {
+            return GUARD_NONE;
+        }
+        return guardMask;
+    }
+
+    /**
+     * True if a non-time_zone affectQueryResult variable differs between the 
two maps.
+     * The union of both key sets is compared: a variable that exists in only 
one map is a mismatch.
+     * A current-only variable (e.g. enable_decimal256, added to the persisted 
plan-variable set after this
+     * view/MV was created) means the object was materialized with the other 
(default) value, so the current
+     * non-default setting must disable rewrite; a persist-only variable 
likewise differs because its value
+     * in the other map is unknown. This restores the conservative whole-map 
comparison semantics.
+     */
+    private static boolean differsInNonTimeZoneVars(Map<String, String> 
currentVars, Map<String, String> persistVars) {
+        if (currentVars == null || persistVars == null) {
+            return false;
+        }
+        for (String key : Sets.union(currentVars.keySet(), 
persistVars.keySet())) {
+            if (SessionVariable.TIME_ZONE.equals(key)) {
+                continue;
+            }
+            if (!Objects.equals(persistVars.get(key), currentVars.get(key))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Whether two time-zone spellings denote the same zone. UTC / Etc/UTC / 
GMT / +00:00 are all the
+     * same instant-zone and must compare equal even though their persisted 
strings differ.
+     */
+    public static boolean timeZonesEquivalent(String tzA, String tzB) {
+        if (Objects.equals(tzA, tzB)) {
+            return true;
+        }
+        if (tzA == null || tzB == null) {
+            return false;
+        }
+        try {
+            return 
ZoneId.of(tzA).normalized().equals(ZoneId.of(tzB).normalized());
+        } catch (DateTimeException e) {
+            return false;
+        }
+    }
+
+    private static Map<String, String> 
currentAffectQueryResultInPlanVariables() {
+        ConnectContext ctx = ConnectContext.get();
+        if (ctx == null || ctx.getSessionVariable() == null) {
+            return ImmutableMap.of();
+        }
+        return ctx.getSessionVariable().getAffectQueryResultInPlanVariables();
+    }
+
+    /**
+     * Whether a stored column expression (a generated column or a synchronous 
materialized view column
+     * definition, including the separately stored WHERE clause) is time-zone 
sensitive: its materialized
+     * value depends on the session time zone because a TIMESTAMPTZ operand 
(possibly nested in a complex
+     * type) is rendered or interpreted in that zone.
+     *
+     * <p>Such expressions cannot be kept consistent: they are evaluated by BE 
in the write/load session
+     * time zone, and the persisted session variables only drive FE 
binding/cache decisions, so an
+     * expression created in one zone and evaluated in another materializes a 
different value (the
+     * {@code SessionVarGuardExpr} wrapper is dropped by {@code 
MergeGuardExpr} and by expression to
+     * thrift translation before execution). They are therefore rejected at 
creation time instead.
+     *
+     * <p>Every rendering of an instant into another representation is 
rejected, including STRING: the
+     * string embeds the write-session offset, so the same instant is stored 
as different strings by
+     * different write sessions and any later grouping/comparison observes 
that difference. Comparing two
+     * TIMESTAMPTZ instants and copying an instant verbatim are zone invariant 
and stay allowed; the
+     * comparison operands are still classified individually because they may 
contain a sensitive cast.
+     */
+    public static boolean isTimeZoneSensitiveStoredExpr(Expr expr) {
+        if (expr == null) {
+            return false;
+        }
+        if (expr instanceof SlotRef || expr instanceof LiteralExpr) {
+            // A bare slot/literal only carries a value; whether that value is 
zone dependent is decided by
+            // the operation that renders or interprets it (a TIMESTAMPTZ slot 
holds a zone-independent
+            // instant, an offset-free string slot holds whatever the storage 
session wrote).
+            return false;
+        }
+        if (expr instanceof IsNullPredicate) {
+            // the boolean result of IS (NOT) NULL does not depend on the time 
zone
+            return false;
+        }
+        if (expr instanceof CastExpr) {
+            return isTimeZoneSensitiveCast((CastExpr) expr);
+        }
+        if (expr instanceof BinaryPredicate && 
isInstantComparison((BinaryPredicate) expr)) {
+            // Comparing two TIMESTAMPTZ instants is zone invariant regardless 
of the operator, but the
+            // operands may themselves be zone-sensitive expressions (e.g. 
date_trunc(ts, 'day') = ts2), so
+            // recurse. A comparison against an offset-free literal is NOT 
exempt: the literal is
+            // interpreted in the write/load session zone and falls through to 
the check below.
+            return isTimeZoneSensitiveStoredExpr(expr.getChild(0))
+                    || isTimeZoneSensitiveStoredExpr(expr.getChild(1));
+        }
+        if (expr instanceof FunctionCallExpr && ((FunctionCallExpr) 
expr).isAggregateFunction()) {
+            // aggregates preserve the UTC instant; only a time-zone 
conversion inside their argument
+            // (e.g. MAX(date_trunc(ts, 'day'))) makes the value zone dependent
+            for (Expr child : expr.getChildren()) {
+                if (isTimeZoneSensitiveStoredExpr(child)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        for (Expr child : expr.getChildren()) {
+            if (containsTimeStampTz(child.getType())) {
+                return true;
+            }
+            if (isTimeZoneSensitiveStoredExpr(child)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Classify a cast by both its source and its result type instead of only 
its children: casting INTO
+     * TIMESTAMPTZ interprets an offset-free source representation 
(string/datetime) in the session zone,
+     * and casting a TIMESTAMPTZ instant OUT of TIMESTAMPTZ renders it in the 
session zone - including into
+     * STRING, whose rendering embeds the write-session offset and is 
therefore not canonical.
+     * TIMESTAMPTZ &lt;-&gt; TIMESTAMPTZ is instant preserving and only 
recurses into the source.
+     */
+    private static boolean isTimeZoneSensitiveCast(CastExpr castExpr) {
+        Expr source = castExpr.getChild(0);
+        boolean sourceTz = containsTimeStampTz(source.getType());
+        boolean targetTz = containsTimeStampTz(castExpr.getType());
+        if (sourceTz != targetTz) {

Review Comment:
   [P1] Compare corresponding leaf types when classifying complex casts
   
   Consider a non-key source `s STRUCT<a:TIMESTAMPTZ(6), b:TIMESTAMPTZ(6)>` and 
`g STRUCT<a:TIMESTAMPTZ(6), b:STRING> GENERATED ALWAYS AS (s)`. This supported 
declared-target cast has `sourceTz=true` and `targetTz=true` because field `a` 
stays TIMESTAMPTZ, so the fence recurses into the safe SlotRef and accepts it. 
BindSink later inserts the actual struct cast; BE preserves `a` but renders `b` 
with RuntimeState's write zone. Identical input instants consequently store 
different strings in UTC and +08:00 loads. The earlier scalar source/target and 
missing implicit-cast issues are fixed; this mixed-field cast is a distinct 
instance that defeats the new presence-only check. Compare corresponding 
array/map/struct leaf types recursively and reject any sensitive leaf 
conversion, with a mixed-field generated-column test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SessionVarGuardRewriter.java:
##########
@@ -139,15 +348,268 @@ public Plan visit(Plan plan, Void ctx) {
 
     /**
      * Check if current query session variables match MV creation session 
variables.
-     * Only compares variables that affect query results.
+     * Only compares variables that affect query results. The time zone is 
compared with its canonical
+     * identity (UTC / Etc/UTC / GMT / +00:00 are the same zone) so equivalent 
spellings do not cause a
+     * mismatch and a resulting unnecessary rewrite loss.
      */
     public static boolean checkSessionVariablesMatch(Map<String, String> 
currentSessionVars,
             Map<String, String> persistSessionVars) {
         if (persistSessionVars == null || persistSessionVars.isEmpty()) {
             // If no session variables saved, consider them matched
             return true;
         }
-        return currentSessionVars.equals(persistSessionVars);
+        for (Map.Entry<String, String> entry : persistSessionVars.entrySet()) {
+            String key = entry.getKey();
+            String persistedValue = entry.getValue();
+            String currentValue = currentSessionVars.get(key);
+            if (SessionVariable.TIME_ZONE.equals(key)) {
+                if (!timeZonesEquivalent(persistedValue, currentValue)) {
+                    return false;
+                }
+            } else if (!Objects.equals(persistedValue, currentValue)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Whether the guard rewriter must be applied to the object owning {@code 
persistSessionVars}: either
+     * some persisted affectQueryResult variable no longer matches the current 
session, or the persisted
+     * map does not carry {@code time_zone} at all. The latter covers 
pre-change metadata whose creation
+     * time zone is unknown, so time-zone sensitive expressions of such 
objects must always be guarded
+     * (a conservative compatibility fence) to avoid cross-zone rewrite of 
stale materialized values.
+     */
+    public static boolean needsSessionVarGuard(Map<String, String> 
currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        return computeGuardMask(currentSessionVars, persistSessionVars) != 
GUARD_NONE;
+    }
+
+    /**
+     * Whether the persisted session-variable map identifies the creation time 
zone.
+     *
+     * <p>A missing, empty or time_zone-less map means the creation zone is 
unknown: the object was created
+     * before time_zone was persisted (old FE), or its metadata was replayed 
by a new FE without the key.
+     * Such an object is the old-metadata -> new-FE upgrade direction of the 
metadata compatibility fence:
+     * its zone-sensitive expressions are guarded forever and it must be 
recreated on the new FE before it
+     * can participate in a cross-zone rewrite. The reverse direction (new 
metadata read by an old FE)
+     * cannot be repaired from the new code: an old read-serving FE neither 
registers this key nor
+     * classifies TIMESTAMPTZ functions, so it keeps building its 
nominal-mismatch cache without the time
+     * zone guard. New-metadata objects therefore have to be rebuilt/recreated 
before an old FE serves
+     * their reads, or the old FE must not be used for reads until it is 
upgraded.
+     */
+    public static boolean isCreationSessionKnown(Map<String, String> 
persistSessionVars) {
+        return persistSessionVars != null && !persistSessionVars.isEmpty()
+                && persistSessionVars.containsKey(SessionVariable.TIME_ZONE);
+    }
+
+    /**
+     * Computes which session-variable dependency families differ between the 
query session and the
+     * persisted (creation) session, as a bitmask of {@link #GUARD_TIME_ZONE} 
and {@link #GUARD_OTHER}.
+     * Per-family scoping lets a time-zone-only difference keep rewriting 
expressions that depend on other
+     * variables (and vice versa).
+     *
+     * <p>An unknown creation session (see {@link #isCreationSessionKnown}) 
always selects the time-zone
+     * family, never an unconditional match.
+     */
+    public static int computeGuardMask(Map<String, String> currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        int mask = GUARD_NONE;
+        // The creation zone is unknown (pre-change metadata, including an 
empty map) or differs from the
+        // current zone.
+        if (!isCreationSessionKnown(persistSessionVars)
+                || 
!timeZonesEquivalent(persistSessionVars.get(SessionVariable.TIME_ZONE),
+                        currentSessionVars == null ? null : 
currentSessionVars.get(SessionVariable.TIME_ZONE))) {
+            mask |= GUARD_TIME_ZONE;
+        }
+        if (persistSessionVars != null && !persistSessionVars.isEmpty()
+                && differsInNonTimeZoneVars(currentSessionVars, 
persistSessionVars)) {
+            mask |= GUARD_OTHER;
+        }
+        return mask;
+    }
+
+    /**
+     * Guard mask for the rewrite cache of a synchronous materialized view.
+     *
+     * <p>Sync MVs keep one deliberate exception: a difference confined to the 
strict-table sql_mode bits
+     * does not change the semantics of the rewritten expressions, so it must 
not disable rewrite (see
+     * {@link #checkSessionVariablesMatchForSyncMv}). The exception is only 
consulted when the mask selects
+     * the non-time-zone family on its own, and it can therefore never 
suppress the time-zone fence: a
+     * legacy sync MV whose persisted map is empty or time_zone-less is still 
guarded for the time zone
+     * (the full mask is returned), so an empty map is never an unconditional 
match and the cache cannot be
+     * rewritten in a different zone.
+     */
+    public static int computeSyncMvGuardMask(Map<String, String> 
currentSessionVars,
+            Map<String, String> persistSessionVars) {
+        int guardMask = computeGuardMask(currentSessionVars, 
persistSessionVars);
+        if (guardMask == GUARD_OTHER
+                && checkSessionVariablesMatchForSyncMv(currentSessionVars, 
persistSessionVars)) {
+            return GUARD_NONE;
+        }
+        return guardMask;
+    }
+
+    /**
+     * True if a non-time_zone affectQueryResult variable differs between the 
two maps.
+     * The union of both key sets is compared: a variable that exists in only 
one map is a mismatch.
+     * A current-only variable (e.g. enable_decimal256, added to the persisted 
plan-variable set after this
+     * view/MV was created) means the object was materialized with the other 
(default) value, so the current
+     * non-default setting must disable rewrite; a persist-only variable 
likewise differs because its value
+     * in the other map is unknown. This restores the conservative whole-map 
comparison semantics.
+     */
+    private static boolean differsInNonTimeZoneVars(Map<String, String> 
currentVars, Map<String, String> persistVars) {
+        if (currentVars == null || persistVars == null) {
+            return false;
+        }
+        for (String key : Sets.union(currentVars.keySet(), 
persistVars.keySet())) {
+            if (SessionVariable.TIME_ZONE.equals(key)) {
+                continue;
+            }
+            if (!Objects.equals(persistVars.get(key), currentVars.get(key))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Whether two time-zone spellings denote the same zone. UTC / Etc/UTC / 
GMT / +00:00 are all the
+     * same instant-zone and must compare equal even though their persisted 
strings differ.
+     */
+    public static boolean timeZonesEquivalent(String tzA, String tzB) {
+        if (Objects.equals(tzA, tzB)) {
+            return true;
+        }
+        if (tzA == null || tzB == null) {
+            return false;
+        }
+        try {
+            return 
ZoneId.of(tzA).normalized().equals(ZoneId.of(tzB).normalized());
+        } catch (DateTimeException e) {
+            return false;
+        }
+    }
+
+    private static Map<String, String> 
currentAffectQueryResultInPlanVariables() {
+        ConnectContext ctx = ConnectContext.get();
+        if (ctx == null || ctx.getSessionVariable() == null) {
+            return ImmutableMap.of();
+        }
+        return ctx.getSessionVariable().getAffectQueryResultInPlanVariables();
+    }
+
+    /**
+     * Whether a stored column expression (a generated column or a synchronous 
materialized view column
+     * definition, including the separately stored WHERE clause) is time-zone 
sensitive: its materialized
+     * value depends on the session time zone because a TIMESTAMPTZ operand 
(possibly nested in a complex
+     * type) is rendered or interpreted in that zone.
+     *
+     * <p>Such expressions cannot be kept consistent: they are evaluated by BE 
in the write/load session
+     * time zone, and the persisted session variables only drive FE 
binding/cache decisions, so an
+     * expression created in one zone and evaluated in another materializes a 
different value (the
+     * {@code SessionVarGuardExpr} wrapper is dropped by {@code 
MergeGuardExpr} and by expression to
+     * thrift translation before execution). They are therefore rejected at 
creation time instead.
+     *
+     * <p>Every rendering of an instant into another representation is 
rejected, including STRING: the
+     * string embeds the write-session offset, so the same instant is stored 
as different strings by
+     * different write sessions and any later grouping/comparison observes 
that difference. Comparing two
+     * TIMESTAMPTZ instants and copying an instant verbatim are zone invariant 
and stay allowed; the
+     * comparison operands are still classified individually because they may 
contain a sensitive cast.
+     */
+    public static boolean isTimeZoneSensitiveStoredExpr(Expr expr) {
+        if (expr == null) {
+            return false;
+        }
+        if (expr instanceof SlotRef || expr instanceof LiteralExpr) {
+            // A bare slot/literal only carries a value; whether that value is 
zone dependent is decided by
+            // the operation that renders or interprets it (a TIMESTAMPTZ slot 
holds a zone-independent
+            // instant, an offset-free string slot holds whatever the storage 
session wrote).
+            return false;
+        }
+        if (expr instanceof IsNullPredicate) {
+            // the boolean result of IS (NOT) NULL does not depend on the time 
zone
+            return false;

Review Comment:
   [P1] Inspect the operand before exempting IS NULL
   
   A generated column such as `g BOOLEAN GENERATED ALWAYS AS ((CASE WHEN 
date_trunc(ts, 'day') = ts2 THEN NULL ELSE 1 END) IS NULL)` passes the new 
fence: DDL analysis preserves the CASE, translation creates 
`IsNullPredicate(CaseExpr(...))`, and this branch returns false without 
visiting it. For `ts = 2024-01-01 00:30Z` and `ts2 = 2024-01-01 00:00Z`, a UTC 
write stores `g=true`, while a +08:00 write stores `g=false` for the same 
instants. BindSink's captured-variable scope does not preserve an 
expression-specific BE zone; translation unwraps the guards and DateTrunc uses 
the write session. This is a different bypass from the previously reported 
direct DateTrunc/cast/WHERE cases, which are now rejected. Recurse into the IS 
NULL operand and add a negative generated-column test for this nullable CASE 
shape.



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