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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AdjustNullable.java:
##########
@@ -289,25 +291,61 @@ public Plan visitLogicalSetOperation(LogicalSetOperation 
setOperation, Map<ExprI
         ImmutableList.Builder<List<SlotReference>> newChildrenOutputs = 
ImmutableList.builder();
         List<Boolean> inputNullable = null;
         if (!setOperation.children().isEmpty()) {
-            inputNullable = 
Lists.newArrayListWithCapacity(setOperation.getOutputs().size());
-            for (int i = 0; i < setOperation.getOutputs().size(); i++) {
-                inputNullable.add(false);
+            // whether the output column can be null depends on the semantics 
of the set operation:
+            // - union: a row is in the result if it appears in any child, so 
the column can be null if any
+            //   child column can be null;
+            // - intersect: a row is in the result only if it appears in every 
child, so the column can be
+            //   null only if every child column can be null;
+            // - except: the result is a subset of the left child(child(0)), 
so the column can be null only
+            //   if the left child column can be null.
+            boolean isIntersect = setOperation instanceof LogicalIntersect;
+            boolean isExcept = setOperation instanceof LogicalExcept;
+            int outputSize = setOperation.getOutputs().size();
+            inputNullable = Lists.newArrayListWithCapacity(outputSize);
+            List<Boolean> foundAllChildren = 
Lists.newArrayListWithCapacity(outputSize);
+            for (int i = 0; i < outputSize; i++) {
+                // true is the identity value of the "and" used by intersect,
+                // false is the identity value of the "or" used by union
+                inputNullable.add(isIntersect);
+                foundAllChildren.add(true);
             }
             for (int i = 0; i < setOperation.arity(); i++) {
                 List<Slot> childOutput = setOperation.child(i).getOutput();
                 List<SlotReference> setChildOutput = 
setOperation.getRegularChildOutput(i);
                 ImmutableList.Builder<SlotReference> newChildOutputs = 
ImmutableList.builder();
                 for (int j = 0; j < setChildOutput.size(); j++) {
+                    Slot childSlot = null;
                     for (Slot slot : childOutput) {
                         if 
(slot.getExprId().equals(setChildOutput.get(j).getExprId())) {
-                            inputNullable.set(j, slot.nullable() || 
inputNullable.get(j));
-                            newChildOutputs.add((SlotReference) slot);
+                            childSlot = slot;
                             break;
                         }
                     }
+                    if (childSlot == null) {
+                        foundAllChildren.set(j, false);
+                        continue;
+                    }
+                    newChildOutputs.add((SlotReference) childSlot);
+                    if (isIntersect) {
+                        inputNullable.set(j, inputNullable.get(j) && 
childSlot.nullable());

Review Comment:
   [P1] Update BE output handling before narrowing set-operation nullability
   
   For populated tables `t_nullable(k INT NULL)` and `t_not_null(k INT NOT 
NULL)`, this produces the following reduced plan for `SELECT k FROM t_nullable 
INTERSECT SELECT k FROM t_not_null`:
   
   ```text
   Intersect(output s NOT NULL)
     Scan(t_nullable.k NULL)
     Scan(t_not_null.k NOT NULL)
   ```
   
   `visitLogicalSetOperation` keeps the original child slots but makes `s` 
non-null. The implementation rules and 
`PhysicalPlanTranslator.visitPhysicalSetOperation` copy that non-null result 
into the output tuple and keep the nullable child expressions. However, 
`SetSharedState::update_build_not_ignore_null` 
(`be/src/exec/pipeline/dependency.cpp:503-512`) still ORs nullability from all 
inputs, yielding true, and `SetSourceLocalState::open` 
(`be/src/exec/operator/set_source_operator.cpp:50-65`) unconditionally rejects 
the false output flag versus that true flag. Thus a valid query that previously 
returned matching rows now fails with `SET operator expects a nullalbe : false 
... output is a nullable : true`, independently of `fe_debug` or whether the 
data actually contains NULL. `SELECT k FROM t_not_null EXCEPT SELECT k FROM 
t_nullable` fails for the same reason.
   
   Please coordinate this narrowing with BE hash-key/output-column handling and 
add populated execution regressions for both operations. Removing only the 
equality check is insufficient: the build columns are still stored/wrapped as 
nullable and copied by the source without a non-null output conversion. The new 
tests stop at `analyze().rewrite()` and cannot catch this execution failure.



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