englefly commented on code in PR #69787:
URL: https://github.com/apache/doris/pull/69787#discussion_r4441671076


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java:
##########
@@ -471,14 +471,125 @@ public Plan visitLogicalSink(LogicalSink<? extends Plan> 
rewrittenPlan, Void con
                     
normalizedOutputExprList.add(normalizeExpression(originalExpression,
                             rewrittenExpression, true));
                 }
-                LogicalProject<Plan> project = new 
LogicalProject<>(normalizedOutputExprList,
-                        rewrittenPlan.child());
-                return rewrittenPlan.withChildren(project);
+                // Set the outputs on the sink itself. A project between the 
sink and the child would only
+                // pass the child outputs through with a changed nullable 
property, and the slot of such a
+                // project does not describe the expression which produces it: 
a rule which merges the
+                // projects, e.g. MergeProjectsCBO, then derives the original 
nullable property again and
+                // the plan no longer matches the logical properties of the 
group it is inserted into.
+                return rewrittenPlan.withOutputExprs(normalizedOutputExprList);
             }
         }, null);
 
     }
 
+    /**
+     * Align the outputs of a plan rewritten by a materialized view with the 
origin query plan, so that both plans
+     * can be alternatives of one memo group.
+     *
+     * <p>An output keeps its exprId. The exprId of the origin output may only 
be substituted
+     * <ul>
+     *     <li>when both plans define that output as the same constant: a 
column which a predicate pins to a
+     *     literal can be materialized with a fresh exprId, and the two plans 
then still compute the same value,
+     *     or</li>
+     *     <li>when the rewritten output describes the same column as the 
origin one: the name and the type have
+     *     to match and the qualifier has to be kept by both sides and be 
equal, so that a same named column of
+     *     another relation (e.g. the two relations of a self join) is not 
accepted.</li>
+     * </ul>
+     * Otherwise the two plans are not known to produce the same columns (e.g. 
a self join can expose two same
+     * named columns of two relations) and the rewritten plan must not be used.
+     */
+    public static Plan alignPlanOutputWithOrigin(Plan rewrittenPlan, Plan 
originPlan) {
+        List<Slot> originOutputs = originPlan.getOutput();
+        List<Slot> rewrittenOutputs = rewrittenPlan instanceof LogicalSink
+                ? ((LogicalSink<?>) rewrittenPlan).child().getOutput()
+                : rewrittenPlan.getOutput();
+        if (originOutputs.size() != rewrittenOutputs.size()) {
+            return null;
+        }
+        Map<ExprId, Expression> originOutputDefinitions = 
collectNamedExpressionDefinitions(originPlan);
+        Map<ExprId, Expression> rewrittenOutputDefinitions = 
collectNamedExpressionDefinitions(rewrittenPlan);
+        List<NamedExpression> alignedOutputs = new 
ArrayList<>(originOutputs.size());
+        for (int i = 0; i < originOutputs.size(); i++) {
+            Slot originSlot = originOutputs.get(i);
+            Slot rewrittenSlot = rewrittenOutputs.get(i);
+            if (rewrittenSlot.getExprId().equals(originSlot.getExprId())) {
+                alignedOutputs.add(rewrittenSlot);
+                continue;
+            }
+            // the slot itself is not a constant, the definition of its exprId 
is
+            Expression originDefinition = 
originOutputDefinitions.get(originSlot.getExprId());
+            Expression rewrittenDefinition = 
rewrittenOutputDefinitions.get(rewrittenSlot.getExprId());
+            if (!isSameConstant(rewrittenDefinition, originDefinition)
+                    && !isSameColumn(rewrittenSlot, originSlot)) {
+                return null;
+            }
+            Expression inner = rewrittenSlot.nullable() == 
originSlot.nullable()
+                    ? rewrittenSlot
+                    : (originSlot.nullable() ? new Nullable(rewrittenSlot) : 
new NonNullable(rewrittenSlot));
+            alignedOutputs.add(new Alias(originSlot.getExprId(), inner, 
originSlot.getName()));
+        }
+        if (rewrittenPlan instanceof LogicalSink) {
+            LogicalSink<?> sink = (LogicalSink<?>) rewrittenPlan;
+            List<NamedExpression> sinkOutputs = alignedOutputs.stream()
+                    .map(output -> (NamedExpression) output.toSlot())
+                    .collect(ImmutableList.toImmutableList());
+            // Only the outputs of the sink have to be aligned, adding a 
project below it would repeat the
+            // outputs of the child with a changed nullable property, see 
normalizeSinkExpressions.
+            return sink.withOutputExprs(sinkOutputs);
+        }
+        return new LogicalProject<>(alignedOutputs, rewrittenPlan);
+    }
+
+    /**
+     * Whether the rewritten output describes the same column as the origin 
one. The rewritten plan is built by the
+     * materialized view rewrite which keeps the position of the query 
outputs, so the name, the type and the
+     * qualifier have to match. The qualifier has to be kept by both sides: a 
missing qualifier can not rule out
+     * that the slot is the same named column of another relation, e.g. the 
two relations of a self join, so an
+     * output without a qualifier is rejected and the rewritten plan is not 
used. This also rejects an output which
+     * is added or removed and shifts the columns.
+     */
+    private static boolean isSameColumn(Slot rewrittenSlot, Slot originSlot) {
+        if (!rewrittenSlot.getName().equalsIgnoreCase(originSlot.getName())
+                || 
!rewrittenSlot.getDataType().equals(originSlot.getDataType())) {
+            return false;
+        }
+        List<String> rewrittenQualifier = rewrittenSlot.getQualifier();
+        List<String> originQualifier = originSlot.getQualifier();
+        return !rewrittenQualifier.isEmpty() && 
rewrittenQualifier.equals(originQualifier);

Review Comment:
   done



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