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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/RuntimeFilterPushDownVisitor.java:
##########
@@ -341,7 +350,8 @@ private boolean 
canPushThroughJoinChild(AbstractPhysicalJoin<? extends Plan, ? e
         // A runtime filter is still safe on the null-generating side if 
generated NULL rows
         // cannot become non-NULL before the parent join condition is 
evaluated. For example,
         // `b.pk = c.pk` rejects generated NULLs, while `coalesce(b.pk, 0) = 
c.pk` may match them.
-        return isNullPropagating(ctx.probeExpr);
+        return NullInputEvaluator.evaluateOnFE(ctx.probeExpr, 
ctx.probeExpr.getInputSlots(),

Review Comment:
   [P1] Preserve the runtime filter origin's NULL-matching semantics
   
   Returning true here assumes the operation that produced the runtime filter 
rejects NULL, but `PushDownContext` also represents NULL-aware set-operation 
filters. For example:
   
   ```text
   INTERSECT
     Project[S.x]                         -- runtime values include NULL and 7
       Scan S
     Project[B.x + CAST(B.x IS NULL AS INT)]
       LeftOuterJoin[A.k = B.k]
         A: {(1)}
         B: {(1,5)}
   ```
   
   Without pushdown, the second input is `{5}` and the intersection has no 
NULL. The set-operation RF rejects 5 at the B scan, however, so the lower join 
loses its physical match and manufactures a NULL row; the project still returns 
NULL and `INTERSECT` now gains NULL (the analogous `EXCEPT` loses it). The old 
structural predicate rejected the nested `IsNull`, while this evaluator admits 
it; complex decoupled `<=>` probes have the same missing-origin problem. Please 
carry a null-rejecting/null-matching property in `PushDownContext` and require 
it before using this NULL proof, or conservatively stop these origins at 
null-generating joins. Add a set-operation/outer-join regression for this tree.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/util/NullInputEvaluator.java:
##########
@@ -0,0 +1,94 @@
+// 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.
+
+package org.apache.doris.nereids.util;
+
+import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
+import org.apache.doris.nereids.rules.expression.rules.FoldConstantRule;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Evaluate an expression after replacing an exact set of slots with typed 
SQL NULLs. */
+public final class NullInputEvaluator {
+
+    /** The only results callers may use to prove behavior for NULL-extended 
inputs. */
+    public enum Result {
+        NULL,
+        FALSE,
+        TRUE,
+        OTHER_NON_NULL,
+        UNKNOWN
+    }
+
+    private NullInputEvaluator() {
+    }
+
+    /**
+     * Replace each supplied slot with a NULL of that slot's data type and 
constant-fold the
+     * resulting expression. Only a fully folded literal is classified. An 
incomplete fold or any
+     * exception is UNKNOWN so optimization callers fail closed.
+     */
+    public static Result evaluate(Expression expression, Set<? extends Slot> 
nullSlots,
+            ExpressionRewriteContext context) {
+        return evaluateInternal(expression, nullSlots, context, false);
+    }
+
+    /**
+     * Evaluate using FE constant-folding rules only. Optimizer safety checks 
use this entry point
+     * so proving NULL behavior never issues a BE-folding RPC.
+     */
+    public static Result evaluateOnFE(Expression expression, Set<? extends 
Slot> nullSlots,
+            ExpressionRewriteContext context) {
+        return evaluateInternal(expression, nullSlots, context, true);
+    }
+
+    private static Result evaluateInternal(Expression expression, Set<? 
extends Slot> nullSlots,
+            ExpressionRewriteContext context, boolean feOnly) {
+        try {
+            Map<Expression, Expression> replacements = new HashMap<>();
+            for (Slot slot : nullSlots) {
+                replacements.put(slot, new NullLiteral(slot.getDataType()));
+            }
+            Expression nullInput = ExpressionUtils.replace(expression, 
replacements);
+            Expression folded = feOnly

Review Comment:
   [P1] Reject non-movable expressions before certifying a NULL fold
   
   This proves the final value after folding, but not that evaluation of the 
original expression can be moved. A reduced reachable runtime-filter tree is:
   
   ```text
   HashJoin[CAST(IF(B.x IS NULL, NULL, assert_true(B.x <> 0, 'bad')) AS INT) = 
C.y]
     LeftOuterJoin[A.k = B.k]
       A: {(1)}
       B: {(1,1), (2,0)}
     C: {(1)}
   ```
   
   The original lower join discards `B(2,0)`, so that row never evaluates 
`assert_true`. After substituting NULL here, FE folding selects the NULL branch 
and returns `Result.NULL`; the runtime-filter caller can then install the 
complete expression at the B scan, where `B(2,0)` throws. 
`Count(IF(...assert_true...))` reaches the analogous eager-aggregation path and 
is evaluated by a pre-aggregate on the discarded base row. The removed 
structural/marker checks rejected both shapes. Please fail closed on the 
original expression's `NoneMovableFunction` contract before folding (or enforce 
that contract in every movement caller), and cover both consumers with 
error-preservation tests.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownProject.java:
##########
@@ -410,6 +428,23 @@ public <E extends Expression> Optional<E> 
pushDownExpression(E expression) {
             }
         }
 
+        private boolean canPushDownThroughChild(Expression expression, int 
childIndex) {
+            if (!(plan instanceof LogicalJoin)) {
+                return true;
+            }
+            JoinType joinType = ((LogicalJoin<?, ?>) plan).getJoinType();
+            boolean nullGeneratingSide = childIndex == 0
+                    ? joinType.isLeftSideNullable() : 
joinType.isRightSideNullable();
+            if (!nullGeneratingSide) {
+                return true;
+            }
+            if (!nullInputEvaluationContext.isPresent()) {
+                return false;
+            }
+            return NullInputEvaluator.evaluate(expression, 
expression.getInputSlots(),

Review Comment:
   [P2] Keep this repeated safety check on FE folding
   
   `NullInputEvaluator.evaluate` enters the full folding pipeline. With 
`enable_fold_constant_by_be=true`, replacing the nullable slot makes an 
FE-incomplete candidate such as `map_contains_key(r.m, 1)` constant, so 
`FoldConstantRuleOnBE` sends a folding RPC and waits up to five seconds. This 
predicate is called separately for every eligible `PreferPushDownProject` 
expression, allowing N candidates to issue N sequential RPCs even though their 
original slot-bearing expressions were not BE-foldable. The PR's other movement 
checks use `evaluateOnFE` specifically to fail closed without RPCs; please use 
that entry point here too and add a caller-level test with an FE-incomplete 
function.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/util/NullInputEvaluator.java:
##########
@@ -0,0 +1,94 @@
+// 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.
+
+package org.apache.doris.nereids.util;
+
+import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
+import org.apache.doris.nereids.rules.expression.rules.FoldConstantRule;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Evaluate an expression after replacing an exact set of slots with typed 
SQL NULLs. */
+public final class NullInputEvaluator {
+
+    /** The only results callers may use to prove behavior for NULL-extended 
inputs. */
+    public enum Result {
+        NULL,
+        FALSE,
+        TRUE,
+        OTHER_NON_NULL,
+        UNKNOWN
+    }
+
+    private NullInputEvaluator() {
+    }
+
+    /**
+     * Replace each supplied slot with a NULL of that slot's data type and 
constant-fold the
+     * resulting expression. Only a fully folded literal is classified. An 
incomplete fold or any
+     * exception is UNKNOWN so optimization callers fail closed.
+     */
+    public static Result evaluate(Expression expression, Set<? extends Slot> 
nullSlots,
+            ExpressionRewriteContext context) {
+        return evaluateInternal(expression, nullSlots, context, false);
+    }
+
+    /**
+     * Evaluate using FE constant-folding rules only. Optimizer safety checks 
use this entry point
+     * so proving NULL behavior never issues a BE-folding RPC.
+     */
+    public static Result evaluateOnFE(Expression expression, Set<? extends 
Slot> nullSlots,
+            ExpressionRewriteContext context) {
+        return evaluateInternal(expression, nullSlots, context, true);
+    }
+
+    private static Result evaluateInternal(Expression expression, Set<? 
extends Slot> nullSlots,
+            ExpressionRewriteContext context, boolean feOnly) {
+        try {
+            Map<Expression, Expression> replacements = new HashMap<>();
+            for (Slot slot : nullSlots) {
+                replacements.put(slot, new NullLiteral(slot.getDataType()));
+            }
+            Expression nullInput = ExpressionUtils.replace(expression, 
replacements);
+            Expression folded = feOnly
+                    ? FoldConstantRule.evaluateOnFE(nullInput, context)
+                    : FoldConstantRule.evaluate(nullInput, context);
+            if (!(folded instanceof Literal)) {
+                return Result.UNKNOWN;
+            }
+            if (folded.isNullLiteral()) {
+                return Result.NULL;
+            }
+            if (BooleanLiteral.FALSE.equals(folded)) {
+                return Result.FALSE;
+            }
+            if (BooleanLiteral.TRUE.equals(folded)) {
+                return Result.TRUE;
+            }
+            return Result.OTHER_NON_NULL;
+        } catch (Exception e) {

Review Comment:
   [P2] Preserve the FE-debug exception contract
   
   This outer catch also absorbs the exception that 
`FoldConstantRule.evaluateInternal` deliberately rethrows when `fe_debug` is 
enabled. Before this migration, `ConflictRulesMaker.isEvalToNullOrFalse` and 
`ExpressionUtils.matchesWhenSlotIsNull` called `FoldConstantRule.evaluate` 
directly, so folding failures surfaced in FE-debug test runs; now both silently 
receive `UNKNOWN`. That contradicts the session variable's documented 'throw 
exceptions instead [of] swallow them' behavior. Please rethrow here when 
`SessionVariable.isFeDebug()` and keep `UNKNOWN` only for ordinary mode, with a 
debug-mode exception 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