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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java:
##########
@@ -255,40 +262,249 @@ private static Set<Expression> 
normalizeExpression(Expression expression, Cascad
     }
 
     /**
-     * compensate residual predicates
+     * collect all predicate compensation candidates before residual 
finalization.
      */
-    public static Map<Expression, ExpressionInfo> 
compensateResidualPredicate(StructInfo queryStructInfo,
+    public static PredicateCompensation collectCompensationCandidates(
+            StructInfo queryStructInfo,
             StructInfo viewStructInfo,
             SlotMapping viewToQuerySlotMapping,
-            ComparisonResult comparisonResult) {
-        // TODO Residual predicates compensate, simplify implementation 
currently.
-        SplitPredicate querySplitPredicate = 
queryStructInfo.getSplitPredicate();
-        SplitPredicate viewSplitPredicate = viewStructInfo.getSplitPredicate();
+            ComparisonResult comparisonResult,
+            CascadesContext cascadesContext) {
+        Map<Expression, ExpressionInfo> equalCandidates = 
collectEquivalenceCandidates(
+                queryStructInfo, viewStructInfo, viewToQuerySlotMapping, 
comparisonResult);
+        Map<Expression, ExpressionInfo> rangeCandidates = 
collectRangeCandidates(
+                queryStructInfo, viewStructInfo, viewToQuerySlotMapping, 
comparisonResult, cascadesContext);
+        Map<Expression, ExpressionInfo> residualCandidates = 
collectResidualCandidates(queryStructInfo);
+        if (equalCandidates == null || rangeCandidates == null || 
residualCandidates == null) {
+            return null;
+        }
+        return new PredicateCompensation(equalCandidates, rangeCandidates, 
residualCandidates);
+    }
 
-        Set<Expression> viewResidualQueryBasedSet = new HashSet<>();
-        for (Expression viewExpression : 
viewSplitPredicate.getResidualPredicateMap().keySet()) {
-            viewResidualQueryBasedSet.add(
-                    ExpressionUtils.replace(viewExpression, 
viewToQuerySlotMapping.toSlotReferenceMap()));
+    /**
+     * compensate predicates in one step.
+     */
+    public static PredicateCompensation compensatePredicates(
+            StructInfo queryStructInfo,
+            StructInfo viewStructInfo,
+            SlotMapping viewToQuerySlotMapping,
+            ComparisonResult comparisonResult,
+            CascadesContext cascadesContext) {
+        PredicateCompensation compensationCandidates = 
collectCompensationCandidates(
+                queryStructInfo, viewStructInfo, viewToQuerySlotMapping, 
comparisonResult, cascadesContext);
+        if (compensationCandidates == null) {
+            return null;
+        }
+        return compensateCandidatesByViewResidual(viewStructInfo, 
viewToQuerySlotMapping,
+                compensationCandidates);
+    }
+
+    /**
+     * Build the final predicate compensation from collected candidates.
+     *
+     * This step uses query-based view residual predicates to:
+     * 1) validate compensation safety (candidates must imply view residual), 
and
+     * 2) remove candidate predicates already covered by view residual.
+     *
+     * Returns null when compensation is unsafe or cannot be proven within DNF 
guard limits.
+     */
+    public static PredicateCompensation compensateCandidatesByViewResidual(
+            StructInfo viewStructInfo,
+            SlotMapping viewToQuerySlotMapping,
+            PredicateCompensation compensationCandidates) {
+        try {
+            return doCompensateCandidatesByViewResidual(viewStructInfo, 
viewToQuerySlotMapping,
+                    compensationCandidates);
+        } catch (DnfBranchOverflowException e) {
+            // DNF branch expansion may explode exponentially; fail 
compensation conservatively.
+            return null;
         }
-        viewResidualQueryBasedSet.remove(BooleanLiteral.TRUE);
+    }
+
+    private static PredicateCompensation doCompensateCandidatesByViewResidual(
+            StructInfo viewStructInfo,
+            SlotMapping viewToQuerySlotMapping,
+            PredicateCompensation compensationCandidates) {
+        Set<Expression> viewResidualPredicatesQueryBased =
+                collectViewResidualPredicates(viewStructInfo, 
viewToQuerySlotMapping);
+        Expression combinedViewResidualQueryBased = 
buildCombinedPredicate(viewResidualPredicatesQueryBased);
 
-        Set<Expression> queryResidualSet = 
querySplitPredicate.getResidualPredicateMap().keySet();
-        // remove unnecessary literal BooleanLiteral.TRUE
-        queryResidualSet.remove(BooleanLiteral.TRUE);
-        // query residual predicate can not contain all view residual 
predicate when view have residual predicate,
-        // bail out
-        if (!queryResidualSet.containsAll(viewResidualQueryBasedSet)) {
+        if (!validateCompensationByViewResidual(combinedViewResidualQueryBased,
+                compensationCandidates)) {
             return null;
         }
-        queryResidualSet.removeAll(viewResidualQueryBasedSet);
-        Map<Expression, ExpressionInfo> expressionExpressionInfoMap = new 
HashMap<>();
-        for (Expression needCompensate : queryResidualSet) {
-            if (needCompensate.anyMatch(AggregateFunction.class::isInstance)) {
+
+        return new PredicateCompensation(
+                removePredicatesImpliedByViewResidual(
+                        compensationCandidates.getEquals(), 
combinedViewResidualQueryBased),
+                removePredicatesImpliedByViewResidual(
+                        compensationCandidates.getRanges(), 
combinedViewResidualQueryBased),
+                removePredicatesImpliedByViewResidual(
+                        compensationCandidates.getResiduals(), 
combinedViewResidualQueryBased));
+    }
+
+    private static Set<Expression> collectViewResidualPredicates(StructInfo 
viewStructInfo,
+            SlotMapping viewToQuerySlotMapping) {
+        Set<Expression> viewResidualPredicates = new LinkedHashSet<>();
+        for (Expression viewExpression : 
viewStructInfo.getSplitPredicate().getResidualPredicateMap().keySet()) {
+            Expression queryBasedExpression =
+                    ExpressionUtils.replace(viewExpression, 
viewToQuerySlotMapping.toSlotReferenceMap());
+            if (!BooleanLiteral.TRUE.equals(queryBasedExpression)) {
+                viewResidualPredicates.add(queryBasedExpression);
+            }
+        }
+        return viewResidualPredicates;
+    }
+
+    private static Map<Expression, ExpressionInfo> 
collectResidualCandidates(StructInfo queryStructInfo) {
+        Collection<Expression> expressions = 
queryStructInfo.getSplitPredicate().getResidualPredicateMap().keySet();
+        Map<Expression, ExpressionInfo> residualPredicates = new 
LinkedHashMap<>();
+        for (Expression expression : expressions) {
+            if (BooleanLiteral.TRUE.equals(expression)) {
+                continue;
+            }
+            // Aggregate functions in residual predicates are not safe for 
detail-MV compensation.
+            if (expression.anyMatch(AggregateFunction.class::isInstance)) {
                 return null;
             }
-            expressionExpressionInfoMap.put(needCompensate, 
ExpressionInfo.EMPTY);
+            residualPredicates.put(expression, ExpressionInfo.EMPTY);
+        }
+        return ImmutableMap.copyOf(residualPredicates);
+    }
+
+    private static boolean validateCompensationByViewResidual(
+            Expression combinedViewResidualQueryBased,
+            PredicateCompensation compensationCandidates) {
+        // Safety check: combinedQueryCandidates must imply viewResidual.
+        Expression combinedQueryCandidates = buildCombinedPredicate(
+                compensationCandidates.getEquals().keySet(),
+                compensationCandidates.getRanges().keySet(),
+                compensationCandidates.getResiduals().keySet());
+        return impliesByDnf(combinedQueryCandidates, 
combinedViewResidualQueryBased);
+    }
+
+    @SafeVarargs
+    private static Expression buildCombinedPredicate(Collection<Expression>... 
predicateCollections) {
+        List<Expression> combinedPredicates = new ArrayList<>();
+        for (Collection<Expression> predicateCollection : 
predicateCollections) {
+            for (Expression predicate : predicateCollection) {
+                if (!BooleanLiteral.TRUE.equals(predicate)) {
+                    combinedPredicates.add(predicate);
+                }
+            }
+        }
+        return ExpressionUtils.and(combinedPredicates);
+    }
+
+    private static Map<Expression, ExpressionInfo> 
removePredicatesImpliedByViewResidual(
+            Map<Expression, ExpressionInfo> predicates,
+            Expression viewResidual) {
+        // Remove candidates already implied by the view residual predicate.
+        Map<Expression, ExpressionInfo> remainingPredicates = new 
LinkedHashMap<>();
+        for (Map.Entry<Expression, ExpressionInfo> entry : 
predicates.entrySet()) {
+            // If viewResidual => candidate, candidate is redundant and can be 
dropped.
+            if (!impliesByDnf(viewResidual, entry.getKey())) {
+                remainingPredicates.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return ImmutableMap.copyOf(remainingPredicates);
+    }
+
+    private static boolean impliesByDnf(Expression source, Expression target) {
+        // Check whether source => target.
+        List<Set<Expression>> sourceBranches = extractDnfBranches(source);
+        List<Set<Expression>> targetBranches = extractDnfBranches(target);
+        if (sourceBranches.isEmpty()) {
+            return true;
+        }
+        if (targetBranches.isEmpty()) {
+            return false;
+        }
+        for (Set<Expression> sourceBranch : sourceBranches) {

Review Comment:
   `impliesByDnf()` still treats implication as exact set containment inside 
each DNF branch. That works for `A OR (B AND C)` vs `A OR B`, but it still 
rejects stronger non-identical predicates. For example, if the query residual 
is `id > 15 OR (score = 1 AND id = 5)` and the view residual is `id > 10 OR 
(score = 1 AND id = 5)`, the first query branch *does* imply the first view 
branch, but `sourceBranch.containsAll(targetBranch)` returns false because `id 
> 15` is not textually equal to `id > 10`. In that case 
`validateCompensationByViewResidual()` still bails out, so this doesn't yet 
deliver the PR goal of handling logically implicative residuals beyond naive 
set containment. Please add a branch-level implication check for comparable 
predicates (at least the range predicates you already normalize elsewhere) and 
cover it with a unit 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]

Reply via email to