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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java:
##########
@@ -334,6 +338,23 @@ public Void visitElementAt(ElementAt elementAt, 
CollectorContext context) {
             Expression fieldName = arguments.get(1);
             DataType fieldType = fieldName.getDataType();
             if (fieldName.isLiteral() && (fieldType.isIntegerLikeType() || 
fieldType.isStringLikeType())) {
+                // element_at(s, 'field') IS NULL has two-layer null semantics:
+                //   s IS NULL OR s.field IS NULL
+                // Always emit META [s, NULL] for the struct-level null map.
+                // Only emit META [s, field, NULL] when the selected field 
itself is nullable.
+                if (context.type == ColumnAccessPathType.META
+                        && 
isUnderIsNull(context.accessPathBuilder.getPathList())) {
+                    StructField field = resolveStructField(
+                            (StructType) first.getDataType(), fieldName);
+                    // Path 1: struct-level null check — bypass field prefix
+                    continueCollectAccessPath(first, copyContext(context));

Review Comment:
   [P1] Do not map expression nullability to a non-null Struct
   
   This unconditional ancestor walk can emit a physical NULL path for a node 
that has no null map. With `s STRUCT<f:INT NOT NULL> NOT NULL`, a 
predicate-only `element_at(s, 'f') IS NULL` emits only typed META `[s,NULL]`: 
`ElementAt` is always nullable, then the non-nullable-field branch returns 
early. The same happens through `element_at(if(id = 1, NULL, s), 'f')`, where 
synthetic `IF` nullability is forwarded to the physical `s` slot. The BE makes 
the root Struct `NULL_MAP_ONLY`, but this slot creates a plain `ColumnStruct`, 
so `StructFileColumnIterator::next_batch` fails its `is_column_nullable(*dst)` 
check. This is distinct from the existing nullable-parent/non-nullable-leaf 
thread: the current fix redirects that case to a real parent null map, whereas 
these paths target a root with no map. Please track physical nullability 
through the ancestor/wrapper path and retain DATA (or disable metadata pruning) 
when no real null map can answer the expression; add direct and IF-wrapped all-
 NOT-NULL regressions.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AccessPathExpressionCollector.java:
##########
@@ -334,6 +338,23 @@ public Void visitElementAt(ElementAt elementAt, 
CollectorContext context) {
             Expression fieldName = arguments.get(1);
             DataType fieldType = fieldName.getDataType();
             if (fieldName.isLiteral() && (fieldType.isIntegerLikeType() || 
fieldType.isStringLikeType())) {
+                // element_at(s, 'field') IS NULL has two-layer null semantics:
+                //   s IS NULL OR s.field IS NULL
+                // Always emit META [s, NULL] for the struct-level null map.
+                // Only emit META [s, field, NULL] when the selected field 
itself is nullable.
+                if (context.type == ColumnAccessPathType.META
+                        && 
isUnderIsNull(context.accessPathBuilder.getPathList())) {
+                    StructField field = resolveStructField(
+                            (StructType) first.getDataType(), fieldName);
+                    // Path 1: struct-level null check — bypass field prefix
+                    continueCollectAccessPath(first, copyContext(context));
+                    if (field != null && !field.isNullable()) {
+                        // Non-nullable leaf: struct-level NULL is sufficient
+                        return null;

Review Comment:
   [P1] Keep the field referenced by the unchanged predicate
   
   The early return drops a field that the filter expression still references. 
For the reduced plan
   
   ```text
   Project(element_at(s, 'g'))
     Filter(element_at(s, 'f') IS NULL)
       Scan(s STRUCT<f:INT NOT NULL,g:INT> NULL)
   ```
   
   the projection records DATA `[s,g]`, line 350 records META `[s,NULL]`, and 
this branch returns without any `f` path. Because the Struct now has a parent 
NULL path plus a partial child, `DataTypeAccessTree.pruneDataType` produces 
`STRUCT<g>`. `SlotTypeReplacer` then rebuilds the unchanged filter on that 
pruned slot with signature reuse disabled, so `ElementAt.structFieldType` 
cannot find `f` (and the materialized Struct could not evaluate it either). A 
parent null map is a valid semantic shortcut only if the predicate is also 
rewritten to test the parent; otherwise preserve `f` in the pruned type/data 
paths. Please add nullable-root sibling-projection regressions for both `IS 
NULL` and `IS NOT NULL`.



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