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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/ExpressionEstimation.java:
##########
@@ -156,6 +161,25 @@ public ColumnStatistic visit(Expression expr, Statistics 
context) {
         return expr.child(0).accept(this, context);
     }
 
+    @Override
+    public ColumnStatistic visitIsNull(IsNull isNull, Statistics context) {
+        if (!isNull.child().getDataType().isFileType()) {
+            return visit(isNull, context);
+        }
+        ColumnStatistic child = estimate(isNull.child(), context);
+        double rowCount = context.getRowCount();
+        boolean canBeTrue = rowCount > 0 && (child.isUnKnown || child.numNulls 
> 0);

Review Comment:
   [P2] Keep FILE null-predicate ranges conservative for conditional values
   
   `visitIf`/`visitCaseWhen` return known statistics with `numNulls=0` even for 
nullable or unknown FILE branches, so this code turns a generic estimate into 
an exact `[0,0]` Boolean range. A reduced reachable plan is:
   
   ```text
   Filter(p = TRUE)
     Project(p := ISNULL(IF(flag, TO_FILE('r', uri), CAST(NULL AS FILE))))
       Scan(flag, uri)
   ```
   
   With a valid S3 resource `r` and all `uri` values NULL, every row has 
`p=TRUE`. The volatile producer keeps the filter above the projection, but 
`estimateColumnEqualToConstant` estimates zero rows because TRUE lies outside 
`[0,0]`. This affects cardinality/costing for positive IS NULL and is distinct 
from the existing NOT/ISNULL thread. Preserve a conservative range when the 
child's null count is estimated, or derive conditional null counts reliably; 
cover nullable IF/CASE projections followed by a filter.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java:
##########
@@ -846,6 +871,74 @@ && hasTimeStampNsCompatibleDateTimeType(argType)) {
         });
     }
 
+    /** Reject FILE operations without backend support, preserving value 
propagation and construction. */
+    public static void checkFileFunction(BoundFunction function) {
+        String name = function.getName().toLowerCase(Locale.ROOT);
+        if (function instanceof Combinator) {
+            checkFileFunction(((Combinator) function).getNestedFunction());
+        }
+        if (function instanceof AggregateFunction && ((AggregateFunction) 
function).isDistinct()) {
+            function.getArguments().forEach(arg -> 
Expression.checkFileKey(arg, "DISTINCT " + name));
+        }
+        if (function instanceof Udf || function instanceof AliasUdf) {
+            if (function instanceof PythonUdf || function instanceof 
PythonUdaf || function instanceof PythonUdtf) {
+                // Replayed functions bypass CREATE validation. Check declared 
types too, since NULL
+                // arguments and scalar arguments cannot expose unsupported 
return/intermediate types.
+                FunctionSignature signature = function.getSignatures().get(0);
+                org.apache.doris.catalog.Type intermediateType = null;
+                if (function instanceof PythonUdaf) {
+                    intermediateType = 
((org.apache.doris.catalog.AggregateFunction)
+                            ((PythonUdaf) 
function).getCatalogFunction()).getIntermediateType();
+                }
+                if (signature.returnType.typeContainsFile()
+                        || 
signature.argumentsTypes.stream().anyMatch(DataType::typeContainsFile)
+                        || (intermediateType != null && 
intermediateType.typeContainsFile())) {
+                    throw new AnalysisException("PYTHON_UDF does not support 
FILE or a type containing FILE");
+                }
+            } else {
+                FunctionSignature signature = function.getSignatures().get(0);
+                if (signature.returnType.typeContainsFile()
+                        || 
signature.argumentsTypes.stream().anyMatch(DataType::typeContainsFile)) {
+                    throw new AnalysisException(name + " does not support FILE 
or a type containing FILE");
+                }
+            }
+        }
+        if (!(function instanceof Count || function instanceof ElementAt || 
function instanceof ToFile
+                || function instanceof ExplodeFile
+                || function instanceof ExplodeFileOuter || 
supportsFileValuePropagation(function))) {
+            function.getArguments().forEach(arg -> {
+                if (arg instanceof Lambda) {
+                    // Higher-order binding packs real input arrays into the 
lambda closure.
+                    Lambda lambda = (Lambda) arg;
+                    Expression.checkFileKey(lambda.getLambdaFunction(), name);
+                    lambda.getLambdaArguments().forEach(argument ->
+                            
Expression.checkFileKey(argument.getArrayExpression(), name));
+                } else {
+                    Expression.checkFileKey(arg, name);
+                }
+            });
+        }
+        // MAP values can carry FILE, but MAP keys still require comparison 
and hashing.
+        if (function.getArguments().stream().anyMatch(arg -> 
arg.getDataType().typeContainsFile())
+                && function.getDataType().isMapType()) {
+            MapType map = (MapType) function.getDataType();
+            if (map.getKeyType().typeContainsFile()) {
+                throw new AnalysisException("FILE cannot be used in MAP keys");
+            }
+        }
+    }
+
+    private static boolean supportsFileValuePropagation(BoundFunction 
function) {
+        // These backend implementations select/copy columns or inspect 
container offsets;
+        // none compares, sorts or hashes FILE values. Keep type-specific 
functions gated.
+        return function instanceof Coalesce || function instanceof If || 
function instanceof Nvl
+                || function instanceof Array || function instanceof 
CreateStruct
+                || function instanceof CreateNamedStruct || function 
instanceof CreateMap
+                || function instanceof ArrayConcat || function instanceof 
ArraySlice || function instanceof Reverse

Review Comment:
   [P2] Avoid repeated constant expansion before enabling FILE array concat
   
   This newly admits `ARRAY_CONCAT` for FILE, but its existing BE kernel calls 
`convert_to_full_column_if_const()` inside the row/argument loops 
(`function_array_concat.cpp:91-99`). A reduced supported shape is:
   
   ```text
   Project(array_concat(array(f), array(if(number % 2 = 0, f, NULL))))
     Numbers(1024)
   ```
   
   Here `f` is a constant valid FILE containing 64 KiB of inline bytes. The 
varying second argument bypasses the all-constant fast path, so the first 
`ColumnConst` is expanded to the entire batch anew for every row. 
`ColumnArray::insert_many_from` reaches `ColumnFile` child insertion and 
`ColumnVarbinary`'s deep arena copy: approximately 64 GiB of inline copying to 
produce 64 MiB of that argument's output for a 1024-row batch (static 
copy-volume arithmetic, not a benchmark). This makes the newly supported FILE 
path quadratic in batch size. Unpack constant arrays once and index their 
single row, or materialize each argument once outside the row loop, before 
enabling this propagation path; cover mixed constant/dynamic arrays with 
populated inline bytes.



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