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


##########
be/src/exprs/lambda_function/varray_map_function.cpp:
##########
@@ -325,45 +414,102 @@ class ArrayMapFunction : public LambdaFunction {
             res_type = children[0]->execute_type(&lambda_block);
 
             if (!result_col) {
-                result_col = res_col->clone_empty();
+                result_col = IColumn::mutate(std::move(res_col));
+            } else {
+                result_col->insert_range_from(*res_col, 0, res_col->size());
             }
-            result_col->insert_range_from(*res_col, 0, res_col->size());
             lambda_block.clear_column_data(column_size);
         } while (args_info.current_row_idx < count);
 
         //4. get the result column after execution, reassemble it into a new 
array column, and return.
-        if (result_type->is_nullable()) {
-            if (res_type->is_nullable()) {
-                result_column = ColumnNullable::create(
-                        ColumnArray::create(std::move(result_col), 
std::move(array_column_offset)),
-                        std::move(outside_null_map));
-            } else {
-                // deal with eg: select array_map(x -> x is null, [null, 1, 
2]);
-                // need to create the nested column null map for column array
-                auto nested_null_map = ColumnUInt8::create(result_col->size(), 
0);
-
-                result_column = ColumnNullable::create(
-                        
ColumnArray::create(ColumnNullable::create(std::move(result_col),
-                                                                   
std::move(nested_null_map)),
-                                            std::move(array_column_offset)),
-                        std::move(outside_null_map));
-            }
-        } else {
-            if (res_type->is_nullable()) {
-                result_column =
-                        ColumnArray::create(std::move(result_col), 
std::move(array_column_offset));
-            } else {
-                auto nested_null_map = ColumnUInt8::create(result_col->size(), 
0);
+        result_column = _create_result_column(std::move(result_col), 
std::move(array_column_offset),
+                                              std::move(outside_null_map), 
res_type, result_type);
+        return Status::OK();
+    }
+
+private:
+    static bool _has_variable_length_column(const VExprSPtr& expr) {
+        return !expr->data_type()->have_maximum_size_of_value() ||
+               std::ranges::any_of(expr->children(), [](const auto& child) {
+                   return _has_variable_length_column(child);
+               });
+    }
 
-                result_column = ColumnArray::create(
-                        ColumnNullable::create(std::move(result_col), 
std::move(nested_null_map)),
-                        std::move(array_column_offset));
+    // A referenced non-const capture is expanded once for every nested array 
element before
+    // lambda evaluation. Expanding the full nested cardinality at once can 
create multi-gigabyte
+    // temporary columns (for example, a 5,000-byte VARCHAR repeated 1,000,000 
times) and exceed
+    // ColumnString's UInt32 offset limit. Keep capture expansion and lambda 
evaluation within the
+    // runtime block budget, while retaining direct nested-input reuse when 
one batch is sufficient.
+    // Rule: use the external row budget if any lambda input, output, or 
intermediate column
+    // is variable-length. Fixed-width columns have predictable memory usage, 
so also apply
+    // the external byte budget to their estimated bytes per row.
+    size_t _calculate_lambda_batch_size(const VExprSPtr& lambda_expr,
+                                        const std::vector<ColumnPtr>& 
lambda_datas,
+                                        const Block* block,
+                                        const std::set<int>& 
required_input_column_ids,
+                                        bool has_row_dependent_captures) const 
{
+        const auto add_bytes_with_saturation = [](size_t current_bytes, size_t 
additional_bytes) {
+            constexpr size_t max_bytes = std::numeric_limits<size_t>::max();
+            return additional_bytes > max_bytes - current_bytes ? max_bytes
+                                                                : 
current_bytes + additional_bytes;
+        };
+
+        if (_has_variable_length_column(lambda_expr)) {

Review Comment:
   [P1] Keep expanded variable-width captures within the byte budget
   
   This early return skips `preferred_block_size_bytes` exactly when a 
row-dependent `STRING`/`ARRAY` capture can be most expensive. For example, with 
`batch_size = 65535`, one non-const 70,000-byte string capture and a 
65,535-element input array take the direct path, which repeats that string into 
at least 4,587,450,000 bytes before evaluating an otherwise fixed-width lambda. 
That exceeds `ColumnString`'s 4,294,967,295-byte offset limit and fails a valid 
query even though the normal block budget is much smaller. Please include the 
measured `get_max_row_byte_size()` of expanded non-const captures (and other 
known variable inputs) in `effective_max_rows`, while retaining a conservative 
policy for unknown intermediates, and add a large variable-capture test that 
proves the work is split below the byte/offset limit.



##########
be/src/exprs/lambda_function/varray_map_function.cpp:
##########
@@ -325,45 +414,102 @@ class ArrayMapFunction : public LambdaFunction {
             res_type = children[0]->execute_type(&lambda_block);
 
             if (!result_col) {
-                result_col = res_col->clone_empty();
+                result_col = IColumn::mutate(std::move(res_col));
+            } else {
+                result_col->insert_range_from(*res_col, 0, res_col->size());
             }
-            result_col->insert_range_from(*res_col, 0, res_col->size());
             lambda_block.clear_column_data(column_size);
         } while (args_info.current_row_idx < count);
 
         //4. get the result column after execution, reassemble it into a new 
array column, and return.
-        if (result_type->is_nullable()) {
-            if (res_type->is_nullable()) {
-                result_column = ColumnNullable::create(
-                        ColumnArray::create(std::move(result_col), 
std::move(array_column_offset)),
-                        std::move(outside_null_map));
-            } else {
-                // deal with eg: select array_map(x -> x is null, [null, 1, 
2]);
-                // need to create the nested column null map for column array
-                auto nested_null_map = ColumnUInt8::create(result_col->size(), 
0);
-
-                result_column = ColumnNullable::create(
-                        
ColumnArray::create(ColumnNullable::create(std::move(result_col),
-                                                                   
std::move(nested_null_map)),
-                                            std::move(array_column_offset)),
-                        std::move(outside_null_map));
-            }
-        } else {
-            if (res_type->is_nullable()) {
-                result_column =
-                        ColumnArray::create(std::move(result_col), 
std::move(array_column_offset));
-            } else {
-                auto nested_null_map = ColumnUInt8::create(result_col->size(), 
0);
+        result_column = _create_result_column(std::move(result_col), 
std::move(array_column_offset),
+                                              std::move(outside_null_map), 
res_type, result_type);
+        return Status::OK();
+    }
+
+private:
+    static bool _has_variable_length_column(const VExprSPtr& expr) {
+        return !expr->data_type()->have_maximum_size_of_value() ||
+               std::ranges::any_of(expr->children(), [](const auto& child) {
+                   return _has_variable_length_column(child);
+               });
+    }
 
-                result_column = ColumnArray::create(
-                        ColumnNullable::create(std::move(result_col), 
std::move(nested_null_map)),
-                        std::move(array_column_offset));
+    // A referenced non-const capture is expanded once for every nested array 
element before
+    // lambda evaluation. Expanding the full nested cardinality at once can 
create multi-gigabyte
+    // temporary columns (for example, a 5,000-byte VARCHAR repeated 1,000,000 
times) and exceed
+    // ColumnString's UInt32 offset limit. Keep capture expansion and lambda 
evaluation within the
+    // runtime block budget, while retaining direct nested-input reuse when 
one batch is sufficient.
+    // Rule: use the external row budget if any lambda input, output, or 
intermediate column
+    // is variable-length. Fixed-width columns have predictable memory usage, 
so also apply
+    // the external byte budget to their estimated bytes per row.
+    size_t _calculate_lambda_batch_size(const VExprSPtr& lambda_expr,
+                                        const std::vector<ColumnPtr>& 
lambda_datas,
+                                        const Block* block,
+                                        const std::set<int>& 
required_input_column_ids,
+                                        bool has_row_dependent_captures) const 
{
+        const auto add_bytes_with_saturation = [](size_t current_bytes, size_t 
additional_bytes) {
+            constexpr size_t max_bytes = std::numeric_limits<size_t>::max();
+            return additional_bytes > max_bytes - current_bytes ? max_bytes
+                                                                : 
current_bytes + additional_bytes;
+        };
+
+        if (_has_variable_length_column(lambda_expr)) {
+            return _lambda_block_budget.max_rows;
+        }
+
+        size_t estimated_lambda_bytes_per_row = 
lambda_expr->estimate_memory(1);

Review Comment:
   [P2] Exclude alias-only lambda references from the byte estimate
   
   `lambda_expr->estimate_memory(1)` charges every `VColumnRef` occurrence as a 
newly allocated value, and the loop below then charges the backing 
`lambda_data` again. Inside `array_map` the lambda is executed with a null 
selector, so those refs only return the existing lambda-block column. For 
example, 64 references to `x` in a fixed-width `greatest(x, ... x)` over 20,000 
nullable INTs with a 1 MiB budget are estimated at roughly 320 bytes/row and 
split into about seven batches, although the single input plus result is only 
about 200 KiB and fits the direct path. Please make this lambda-local estimate 
treat forwarding refs (and the forwarding lambda wrapper) as aliases rather 
than materialized intermediates, without changing selector-aware estimates 
globally, and add an adaptive-budget test with repeated argument refs.



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