github-actions[bot] commented on code in PR #66795:
URL: https://github.com/apache/doris/pull/66795#discussion_r4014756766
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateMTMVInfo.java:
##########
@@ -172,6 +174,22 @@ public void analyze(ConnectContext ctx) throws Exception {
// the MV is physically created with IVM metadata.
enableIvm = isExplicitIncremental();
analyzeQuery(ctx);
+ // For a TIMESTAMPTZ-partitioned MTMV (date_trunc on a timestamptz
column) the derived partition
+ // boundaries are UTC-aligned (see MTMVPartitionExprDateTrunc).
Persist time_zone=UTC as the effective
+ // creation zone so the background refresh evaluates the partition
key in UTC (matching the UTC
+ // boundaries) and the rewrite guard compares queries against the
same UTC semantics. Without this,
+ // content expressions would be materialized in a zone different
from the persisted one, and queries
+ // in the persisted (non-UTC) zone could rewrite against a cache
built with different semantics.
+ if (usesUtcTimeZonePartition()) {
Review Comment:
[P1] Normalize the effective zone after AUTO analysis too
This block runs only for non-AUTO refresh methods. For `REFRESH AUTO`,
`analyzeAutoRefreshQuery` probes IVM; an aliased `date_trunc(TIMESTAMPTZ)`
partition is classified as EXPR, rejected by `validateIvmPartition`, and then
succeeds through the regular fallback path without ever returning here.
`OlapTableFactory` consequently persists the non-UTC DDL session map, and every
later refresh context restores it even though `MTMVPartitionExprDateTrunc`
generated UTC-aligned physical boundaries. With a `+08:00` definition, a
`2024-01-01 00:30Z` row truncates to `2024-01-01 00:00+08` (`2023-12-31
16:00Z`), so refresh can fail with no matching sink partition or route it into
the preceding UTC partition.
Please apply this normalization once after either analysis arm has finalized
`mvPartitionInfo` (before the single required partition-descriptor generation),
and add a non-UTC `REFRESH AUTO` regression with an aliased `date_trunc`
partition and a UTC-boundary row.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java:
##########
@@ -1062,6 +1102,185 @@ protected boolean checkIfRewritten(Plan plan,
MaterializationContext context) {
&&
context.alreadyRewrite(plan.getGroupExpression().get().getOwnerGroup().getGroupId());
}
+ /**
+ * Whether the materialized view plan carries a cache guard in a row-set /
grouping / ordering
+ * affecting position: a filter or join predicate, a group-by key, an
order-by key or a window
+ * partition/order key. Such a guard means the materialized rows / groups
were derived with the
+ * creation session semantics, so a query in a different session can never
compensate them and the
+ * rewrite must be rejected regardless of which outputs the query selects.
+ */
+ private static boolean containsCacheGuardInRowAffectingExpressions(Plan
plan) {
+ for (Plan node : plan.<Plan>collectToList(p -> true)) {
+ List<Expression> expressions = new ArrayList<>();
+ if (node instanceof LogicalFilter) {
+ expressions.addAll(((LogicalFilter<?>) node).getConjuncts());
+ } else if (node instanceof LogicalJoin) {
+ LogicalJoin<?, ?> join = (LogicalJoin<?, ?>) node;
+ expressions.addAll(join.getHashJoinConjuncts());
+ expressions.addAll(join.getOtherJoinConjuncts());
+ } else if (node instanceof LogicalAggregate) {
+ expressions.addAll(((LogicalAggregate<?>)
node).getGroupByExpressions());
+ } else if (node instanceof LogicalSort) {
+ ((LogicalSort<?>) node).getOrderKeys().stream()
+ .map(OrderKey::getExpr).forEach(expressions::add);
+ } else if (node instanceof LogicalWindow) {
+ expressions.addAll(((LogicalWindow<?>)
node).getWindowExpressions());
+ }
+ if (containsCacheGuard(expressions)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Whether the rewritten plan consumes a cache-guarded value. The cache
guard lives on the view's
+ * logical output expression (e.g.
Alias(SessionVarGuardExpr(date_trunc(...)))) and its key in the
+ * shuttled output to MV scan mapping. Once the rewrite maps a query
output onto such an expression
+ * the guard itself disappears from the plan in one of two ways: either
the query output is mapped
+ * directly to the materialized column (the rewritten plan references the
guarded MV scan slot), or -
+ * because a query-side nested-object guard never equals the cache guard -
the expression is
+ * recomputed from the guarded output's base columns (the rewritten plan
contains the unguarded
+ * recomputation pattern, e.g. date_trunc(ts#3, 'day') for the guarded
output date_trunc(ts#0,'day')
+ * whose base column ts#0 materializes ts#3). Both consume a value that
the cache declares
+ * creation-session dependent and must be rejected. Guards on view outputs
the query does not read
+ * never reach the rewritten plan and are harmless, which is what makes a
projection-subset rewrite
+ * (selecting only the zone-invariant columns of a UTC MV) safe.
+ */
+ private static boolean rewrittenPlanReadsGuardedOutput(Plan rewrittenPlan,
+ MaterializationContext materializationContext) {
+ if (containsCacheGuard(rewrittenPlan)) {
+ // a cache guard survived into the rewritten plan, e.g. a
compensated predicate that reads the
+ // creation-session value directly
+ return true;
+ }
+ ExpressionMapping shuttledExprToScanExprMapping =
materializationContext.getShuttledExprToScanExprMapping();
+ if (shuttledExprToScanExprMapping == null) {
+ return false;
+ }
+ Multimap<Expression, Expression> outputToScanMapping =
shuttledExprToScanExprMapping.getExpressionMapping();
+ // base column mapping: the view's shuttled base columns (slots) ->
the MV scan columns that
+ // materialize them, e.g. ts#0 -> ts#3
+ Map<Expression, Expression> baseColumnToScanExpr = new HashMap<>();
+ for (Map.Entry<Expression, Expression> entry :
outputToScanMapping.entries()) {
+ if (entry.getKey() instanceof Slot && entry.getValue() instanceof
Slot) {
+ baseColumnToScanExpr.put(entry.getKey(), entry.getValue());
+ }
+ }
+ // the MV scan columns that directly materialize a cache-guarded view
output, and the unguarded
+ // recomputation patterns (base columns already mapped to MV scan
columns) of those outputs
+ Set<Slot> guardedScanSlots = new HashSet<>();
+ Set<Expression> guardedRecomputePatterns = new HashSet<>();
+ for (Expression outputExpr : outputToScanMapping.keySet()) {
+ Optional<SessionVarGuardExpr> guard =
outputExpr.collectFirst(SessionVarGuardExpr.class::isInstance);
+ if (!guard.isPresent() || !guard.get().isCacheGuard()) {
+ continue;
+ }
+ for (Expression scanExpr : outputToScanMapping.get(outputExpr)) {
+ if (scanExpr instanceof Slot) {
+ guardedScanSlots.add((Slot) scanExpr);
+ }
+ }
+ // the guarded shuttled output expression may nest guards (e.g. a
cast over a guarded child),
+ // strip every guard to get the value-shape, then map the base
columns to MV scan columns
+ guardedRecomputePatterns.add(ExpressionUtils.replace(
Review Comment:
[P2] Do not reject safe recomputation from a raw MV column
For an MV `SELECT ts, date_trunc(ts, 'day') AS d FROM t`, the guarded cache
has two independent mappings:
```text
ts -> mv_scan.ts
cacheGuard(date_trunc(ts, 'day')) -> mv_scan.d
```
A cross-zone query for `date_trunc(ts, 'day')` cannot match the guarded
whole-expression key, so `rewriteExpression` descends and safely produces this
reduced plan:
```text
Project(date_trunc(mv_scan.ts, 'day'))
Scan(mv)
```
That evaluates from the raw TIMESTAMPTZ instant in the current query session
and never reads `mv_scan.d`. Adding exactly this expression to
`guardedRecomputePatterns`, and matching the same value shape again in
`queryOutputReadsGuardedOutput`, conflates safe recomputation with consuming
the creation-zone materialized output and disables a valid rewrite. Please make
the fence follow the actually selected MV scan-slot lineage: direct `mv_scan.d`
use must remain rejected, while recomputation using only independent unguarded
scan slots should be allowed and covered by a cross-zone rewrite-success test.
--
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]