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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java:
##########
@@ -1019,12 +1025,75 @@ public Expression visitMatch(Match match, 
ExpressionRewriteContext context) {
                     right.toSql(), match.toSql(), right.getDataType()));
         }
 
-        if (left.getDataType().isVariantType()) {
-            left = new Cast(left, right.getDataType());
+        // Only direct roots use leaf-token semantics. Ordinary object 
subpaths keep their
+        // existing JSON-to-STRING scalar semantics, including when pruning is 
disabled.
+        if (left.getDataType().isVariantType()
+                && (!(left instanceof SlotReference) || ((SlotReference) 
left).hasSubColPath()
+                    || (currentPlan != null && isScalarVariantMatchInput(left, 
currentPlan, new HashMap<>()))
+                    || (match.op() != Operator.MATCH_ANY && match.op() != 
Operator.MATCH_ALL))) {
+            left = TypeCoercionUtils.castIfNotSameType(left, 
StringType.INSTANCE);
         }
+
         return match.withChildren(left, right);
     }
 
+    // Alias and set-operation outputs can have neither a subpath nor a source 
column.
+    // Prove that every producer is scalar before using STRING semantics. An 
unknown
+    // producer or any actual root keeps VARIANT semantics and must bind an 
analyzer.
+    private boolean isScalarVariantMatchInput(Expression input, Plan plan, 
Map<Slot, Boolean> scalarCteOutputs) {
+        // Set-operation coercion can cast both root columns to a common 
VARIANT
+        // layout. That changes storage properties, not root/subpath lineage.
+        if (input instanceof Cast && input.getDataType().isVariantType()
+                && input.child(0).getDataType().isVariantType()) {
+            return isScalarVariantMatchInput(input.child(0), plan, 
scalarCteOutputs);
+        }
+        if (!(input instanceof SlotReference)) {
+            return true;
+        }
+        SlotReference slot = (SlotReference) input;
+        if (slot.hasSubColPath()) {
+            return true;
+        }
+        if (plan instanceof LogicalCTEConsumer) {
+            LogicalCTEConsumer consumer = (LogicalCTEConsumer) plan;
+            Slot producerSlot = consumer.getProducerSlot(slot);
+            Boolean scalar = scalarCteOutputs.get(producerSlot);
+            if (scalar != null) {
+                return scalar;
+            }
+            LogicalCTEProducer<?> producer = 
getCascadesContext().getStatementContext()
+                    .getCteProducerByCteId(consumer.getCteId());
+            // Shared CTE chains form a DAG; do not expand the same producer 
at every reference.
+            scalar = isScalarVariantMatchInput(producerSlot, producer.child(), 
scalarCteOutputs);
+            scalarCteOutputs.put(producerSlot, scalar);
+            return scalar;
+        }
+        if (plan instanceof LogicalSetOperation) {
+            LogicalSetOperation setOperation = (LogicalSetOperation) plan;
+            int index = setOperation.getOutput().indexOf(slot);
+            if (index >= 0) {
+                for (int i = 0; i < setOperation.arity(); i++) {
+                    if 
(!isScalarVariantMatchInput(setOperation.getRegularChildrenOutputs().get(i).get(index),
+                            setOperation.child(i), scalarCteOutputs)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        for (Expression expression : plan.getExpressions()) {
+            if (expression instanceof Alias && ((Alias) 
expression).getExprId().equals(slot.getExprId())) {
+                return isScalarVariantMatchInput(expression.child(0), plan, 
scalarCteOutputs);
+            }
+        }
+        for (Plan child : plan.children()) {
+            if (child.getOutputSet().contains(slot)) {
+                return isScalarVariantMatchInput(slot, child, 
scalarCteOutputs);
+            }
+        }
+        return false;

Review Comment:
   [P2] Preserve scalar MATCH for generated VARIANT slots
   
   With `enable_match_without_inverted_index=true` and `t.v` containing 
`["hello"]`, this scalar projection now fails:
   ```sql
   SELECT element MATCH_ANY 'hello'
   FROM t LATERAL VIEW explode_variant_array(v) e AS element;
   ```
   Its reduced plan is:
   ```text
   Project(element#2 MATCH_ANY 'hello')
     Project(g#3 AS element#2)
       Generate(explode_variant_array(v#1) AS g#3)
         Scan(t.v#1)
   ```
   The helper follows the alias to `g#3`, but 
`LogicalGenerate.getExpressions()` exposes generator functions rather than an 
Alias defining that slot, and `g#3` is absent from the scan's outputs. This 
fallback returns false, so `visitMatch` omits the previous STRING cast. The 
generated slot has no base-column metadata and `ExpressionTranslator` then 
rejects it with `no unambiguous index binding`. The supported BE 
explode/string-cast/scalar-MATCH path still handles this value.
   
   Recursive CTEs carrying `v['obj']` through `UNION ALL` hit the same 
classification gap: `LogicalRecursiveUnion` extends `LogicalBinary`, its fresh 
output slots cannot reach the added regular-set-operation or Alias handling, 
and their MATCH projections are also rejected. These are separate producer 
boundaries from the repaired ordinary alias case. Extend the scalar proof to 
generator and recursive-union/worktable outputs and add regression cases for 
both.



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