Coverage Report

Created: 2026-07-22 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exprs/vectorized_fn_call.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "exprs/vectorized_fn_call.h"
19
20
#include <fmt/compile.h>
21
#include <fmt/format.h>
22
#include <fmt/ranges.h> // IWYU pragma: keep
23
#include <gen_cpp/Opcodes_types.h>
24
#include <gen_cpp/Types_types.h>
25
26
#include <memory>
27
#include <optional>
28
#include <ostream>
29
#include <set>
30
31
#include "common/compare.h"
32
#include "common/config.h"
33
#include "common/exception.h"
34
#include "common/logging.h"
35
#include "common/status.h"
36
#include "common/utils.h"
37
#include "core/assert_cast.h"
38
#include "core/block/block.h"
39
#include "core/block/column_numbers.h"
40
#include "core/column/column.h"
41
#include "core/column/column_array.h"
42
#include "core/column/column_nullable.h"
43
#include "core/column/column_vector.h"
44
#include "core/data_type/data_type.h"
45
#include "core/data_type/data_type_agg_state.h"
46
#include "core/types.h"
47
#include "exec/common/util.hpp"
48
#include "exec/pipeline/pipeline_task.h"
49
#include "exprs/function/array/function_array_distance.h"
50
#include "exprs/function/function_agg_state.h"
51
#include "exprs/function/function_fake.h"
52
#include "exprs/function/function_java_udf.h"
53
#include "exprs/function/function_python_udf.h"
54
#include "exprs/function/function_rpc.h"
55
#include "exprs/function/simple_function_factory.h"
56
#include "exprs/function_context.h"
57
#include "exprs/varray_literal.h"
58
#include "exprs/vcast_expr.h"
59
#include "exprs/vexpr_context.h"
60
#include "exprs/virtual_slot_ref.h"
61
#include "exprs/vliteral.h"
62
#include "runtime/runtime_state.h"
63
#include "storage/index/ann/ann_index.h"
64
#include "storage/index/ann/ann_index_iterator.h"
65
#include "storage/index/ann/ann_search_params.h"
66
#include "storage/index/index_reader.h"
67
#include "storage/index/zone_map/zonemap_eval_context.h"
68
#include "storage/segment/column_reader.h"
69
#include "storage/segment/virtual_column_iterator.h"
70
71
namespace doris {
72
class RowDescriptor;
73
class RuntimeState;
74
class TExprNode;
75
} // namespace doris
76
77
namespace doris {
78
79
const std::string AGG_STATE_SUFFIX = "_state";
80
81
// Now left child is a function call, we need to check if it is a distance function
82
const static std::set<std::string> DISTANCE_FUNCS = {L2DistanceApproximate::name,
83
                                                     InnerProductApproximate::name};
84
const static std::set<TExprOpcode::type> OPS_FOR_ANN_RANGE_SEARCH = {
85
        TExprOpcode::GE, TExprOpcode::LE, TExprOpcode::LE, TExprOpcode::GT, TExprOpcode::LT};
86
87
namespace {
88
89
enum class RawComparisonOp : uint8_t { EQ, NE, LT, LE, GT, GE };
90
91
32
std::optional<RawComparisonOp> raw_comparison_op(std::string_view function_name, bool reverse) {
92
32
    RawComparisonOp op;
93
32
    if (function_name == "eq") {
94
2
        op = RawComparisonOp::EQ;
95
30
    } else if (function_name == "ne") {
96
0
        op = RawComparisonOp::NE;
97
30
    } else if (function_name == "lt") {
98
9
        op = RawComparisonOp::LT;
99
21
    } else if (function_name == "le") {
100
0
        op = RawComparisonOp::LE;
101
21
    } else if (function_name == "gt") {
102
13
        op = RawComparisonOp::GT;
103
13
    } else if (function_name == "ge") {
104
8
        op = RawComparisonOp::GE;
105
8
    } else {
106
0
        return std::nullopt;
107
0
    }
108
32
    if (!reverse || op == RawComparisonOp::EQ || op == RawComparisonOp::NE) {
109
31
        return op;
110
31
    }
111
1
    switch (op) {
112
1
    case RawComparisonOp::LT:
113
1
        return RawComparisonOp::GT;
114
0
    case RawComparisonOp::LE:
115
0
        return RawComparisonOp::GE;
116
0
    case RawComparisonOp::GT:
117
0
        return RawComparisonOp::LT;
118
0
    case RawComparisonOp::GE:
119
0
        return RawComparisonOp::LE;
120
0
    case RawComparisonOp::EQ:
121
0
    case RawComparisonOp::NE:
122
0
        break;
123
1
    }
124
0
    __builtin_unreachable();
125
1
}
126
127
template <typename T, PrimitiveType PT>
128
void execute_raw_comparison(const uint8_t* values, size_t num_values, const Field& literal,
129
11
                            RawComparisonOp op, uint8_t* matches) {
130
11
    const T rhs = literal.get<PT>();
131
53
    for (size_t row = 0; row < num_values; ++row) {
132
42
        if (matches[row] == 0) {
133
3
            continue;
134
3
        }
135
39
        const T lhs = unaligned_load<T>(values + row * sizeof(T));
136
39
        bool keep = false;
137
39
        switch (op) {
138
4
        case RawComparisonOp::EQ:
139
4
            keep = Compare::equal(lhs, rhs);
140
4
            break;
141
0
        case RawComparisonOp::NE:
142
0
            keep = Compare::not_equal(lhs, rhs);
143
0
            break;
144
7
        case RawComparisonOp::LT:
145
7
            keep = Compare::less(lhs, rhs);
146
7
            break;
147
0
        case RawComparisonOp::LE:
148
0
            keep = Compare::less_equal(lhs, rhs);
149
0
            break;
150
18
        case RawComparisonOp::GT:
151
18
            keep = Compare::greater(lhs, rhs);
152
18
            break;
153
10
        case RawComparisonOp::GE:
154
10
            keep = Compare::greater_equal(lhs, rhs);
155
10
            break;
156
39
        }
157
39
        matches[row] = static_cast<uint8_t>(keep);
158
39
    }
159
11
}
vectorized_fn_call.cpp:_ZN5doris12_GLOBAL__N_122execute_raw_comparisonIiLNS_13PrimitiveTypeE5EEEvPKhmRKNS_5FieldENS0_15RawComparisonOpEPh
Line
Count
Source
129
9
                            RawComparisonOp op, uint8_t* matches) {
130
9
    const T rhs = literal.get<PT>();
131
43
    for (size_t row = 0; row < num_values; ++row) {
132
34
        if (matches[row] == 0) {
133
3
            continue;
134
3
        }
135
31
        const T lhs = unaligned_load<T>(values + row * sizeof(T));
136
31
        bool keep = false;
137
31
        switch (op) {
138
0
        case RawComparisonOp::EQ:
139
0
            keep = Compare::equal(lhs, rhs);
140
0
            break;
141
0
        case RawComparisonOp::NE:
142
0
            keep = Compare::not_equal(lhs, rhs);
143
0
            break;
144
7
        case RawComparisonOp::LT:
145
7
            keep = Compare::less(lhs, rhs);
146
7
            break;
147
0
        case RawComparisonOp::LE:
148
0
            keep = Compare::less_equal(lhs, rhs);
149
0
            break;
150
14
        case RawComparisonOp::GT:
151
14
            keep = Compare::greater(lhs, rhs);
152
14
            break;
153
10
        case RawComparisonOp::GE:
154
10
            keep = Compare::greater_equal(lhs, rhs);
155
10
            break;
156
31
        }
157
31
        matches[row] = static_cast<uint8_t>(keep);
158
31
    }
159
9
}
Unexecuted instantiation: vectorized_fn_call.cpp:_ZN5doris12_GLOBAL__N_122execute_raw_comparisonIlLNS_13PrimitiveTypeE6EEEvPKhmRKNS_5FieldENS0_15RawComparisonOpEPh
vectorized_fn_call.cpp:_ZN5doris12_GLOBAL__N_122execute_raw_comparisonIfLNS_13PrimitiveTypeE8EEEvPKhmRKNS_5FieldENS0_15RawComparisonOpEPh
Line
Count
Source
129
2
                            RawComparisonOp op, uint8_t* matches) {
130
2
    const T rhs = literal.get<PT>();
131
10
    for (size_t row = 0; row < num_values; ++row) {
132
8
        if (matches[row] == 0) {
133
0
            continue;
134
0
        }
135
8
        const T lhs = unaligned_load<T>(values + row * sizeof(T));
136
8
        bool keep = false;
137
8
        switch (op) {
138
4
        case RawComparisonOp::EQ:
139
4
            keep = Compare::equal(lhs, rhs);
140
4
            break;
141
0
        case RawComparisonOp::NE:
142
0
            keep = Compare::not_equal(lhs, rhs);
143
0
            break;
144
0
        case RawComparisonOp::LT:
145
0
            keep = Compare::less(lhs, rhs);
146
0
            break;
147
0
        case RawComparisonOp::LE:
148
0
            keep = Compare::less_equal(lhs, rhs);
149
0
            break;
150
4
        case RawComparisonOp::GT:
151
4
            keep = Compare::greater(lhs, rhs);
152
4
            break;
153
0
        case RawComparisonOp::GE:
154
0
            keep = Compare::greater_equal(lhs, rhs);
155
0
            break;
156
8
        }
157
8
        matches[row] = static_cast<uint8_t>(keep);
158
8
    }
159
2
}
Unexecuted instantiation: vectorized_fn_call.cpp:_ZN5doris12_GLOBAL__N_122execute_raw_comparisonIdLNS_13PrimitiveTypeE9EEEvPKhmRKNS_5FieldENS0_15RawComparisonOpEPh
160
161
} // namespace
162
163
768k
VectorizedFnCall::VectorizedFnCall(const TExprNode& node) : VExpr(node) {
164
768k
    _function_name = _fn.name.function_name;
165
768k
}
166
167
Status VectorizedFnCall::prepare(RuntimeState* state, const RowDescriptor& desc,
168
790k
                                 VExprContext* context) {
169
790k
    RETURN_IF_ERROR_OR_PREPARED(VExpr::prepare(state, desc, context));
170
790k
    ColumnsWithTypeAndName argument_template;
171
790k
    argument_template.reserve(_children.size());
172
1.54M
    for (auto child : _children) {
173
1.54M
        if (child->is_literal()) {
174
            // For some functions, he needs some literal columns to derive the return type.
175
739k
            auto literal_node = std::dynamic_pointer_cast<VLiteral>(child);
176
739k
            argument_template.emplace_back(literal_node->get_column_ptr(), child->data_type(),
177
739k
                                           child->expr_name());
178
809k
        } else {
179
809k
            argument_template.emplace_back(nullptr, child->data_type(), child->expr_name());
180
809k
        }
181
1.54M
    }
182
183
790k
    _expr_name = fmt::format("VectorizedFnCall[{}](arguments={},return={})", _fn.name.function_name,
184
790k
                             get_child_names(), _data_type->get_name());
185
790k
    if (_fn.binary_type == TFunctionBinaryType::RPC) {
186
0
        _function = FunctionRPC::create(_fn, argument_template, _data_type);
187
790k
    } else if (_fn.binary_type == TFunctionBinaryType::JAVA_UDF) {
188
529
        if (config::enable_java_support) {
189
529
            if (_fn.is_udtf_function) {
190
                // fake function. it's no use and can't execute.
191
56
                auto builder =
192
56
                        std::make_shared<DefaultFunctionBuilder>(FunctionFake<UDTFImpl>::create());
193
56
                _function = builder->build(argument_template, std::make_shared<DataTypeUInt8>());
194
473
            } else {
195
473
                _function = JavaFunctionCall::create(_fn, argument_template, _data_type);
196
473
            }
197
529
        } else {
198
0
            return Status::InternalError(
199
0
                    "Java UDF is not enabled, you can change be config enable_java_support to true "
200
0
                    "and restart be.");
201
0
        }
202
789k
    } else if (_fn.binary_type == TFunctionBinaryType::PYTHON_UDF) {
203
670
        if (config::enable_python_udf_support) {
204
670
            if (_fn.is_udtf_function) {
205
                // fake function. it's no use and can't execute.
206
                // Python UDTF is executed via PythonUDTFFunction in table function path
207
286
                auto builder =
208
286
                        std::make_shared<DefaultFunctionBuilder>(FunctionFake<UDTFImpl>::create());
209
286
                _function = builder->build(argument_template, std::make_shared<DataTypeUInt8>());
210
384
            } else {
211
384
                _function = PythonFunctionCall::create(_fn, argument_template, _data_type);
212
384
                LOG(INFO) << fmt::format(
213
384
                        "create python function call: {}, runtime version: {}, function code: {}",
214
384
                        _fn.name.function_name, _fn.runtime_version, _fn.function_code);
215
384
            }
216
670
        } else {
217
0
            return Status::InternalError(
218
0
                    "Python UDF is not enabled, you can change be config enable_python_udf_support "
219
0
                    "to true and restart be.");
220
0
        }
221
789k
    } else if (_fn.binary_type == TFunctionBinaryType::AGG_STATE) {
222
748
        DataTypes argument_types;
223
1.09k
        for (auto column : argument_template) {
224
1.09k
            argument_types.emplace_back(column.type);
225
1.09k
        }
226
227
748
        if (match_suffix(_fn.name.function_name, AGG_STATE_SUFFIX)) {
228
748
            if (_data_type->is_nullable()) {
229
0
                return Status::InternalError("State function's return type must be not nullable");
230
0
            }
231
748
            if (_data_type->get_primitive_type() != PrimitiveType::TYPE_AGG_STATE) {
232
0
                return Status::InternalError(
233
0
                        "State function's return type must be agg_state but get {}",
234
0
                        _data_type->get_family_name());
235
0
            }
236
748
            _function = FunctionAggState::create(
237
748
                    argument_types, _data_type,
238
748
                    assert_cast<const DataTypeAggState*>(_data_type.get())->get_nested_function());
239
748
        } else {
240
0
            return Status::InternalError("Function {} is not endwith '_state'", _fn.signature);
241
0
        }
242
788k
    } else {
243
        // get the function. won't prepare function.
244
788k
        _function = SimpleFunctionFactory::instance().get_function(
245
788k
                _fn.name.function_name, argument_template, _data_type,
246
788k
                {.new_version_unix_timestamp = state->query_options().new_version_unix_timestamp},
247
788k
                state->be_exec_version());
248
788k
    }
249
790k
    if (_function == nullptr) {
250
2
        return Status::InternalError("Could not find function {}, arg {} return {} ",
251
2
                                     _fn.name.function_name, get_child_type_names(),
252
2
                                     _data_type->get_name());
253
2
    }
254
790k
    VExpr::register_function_context(state, context);
255
790k
    _function_name = _fn.name.function_name;
256
790k
    _prepare_finished = true;
257
258
790k
    FunctionContext* fn_ctx = context->fn_context(_fn_context_index);
259
790k
    if (fn().__isset.dict_function) {
260
95
        fn_ctx->set_dict_function(fn().dict_function);
261
95
    }
262
790k
    return Status::OK();
263
790k
}
264
265
Status VectorizedFnCall::open(RuntimeState* state, VExprContext* context,
266
2.09M
                              FunctionContext::FunctionStateScope scope) {
267
2.09M
    DCHECK(_prepare_finished);
268
3.99M
    for (auto& i : _children) {
269
3.99M
        RETURN_IF_ERROR(i->open(state, context, scope));
270
3.99M
    }
271
2.09M
    RETURN_IF_ERROR(VExpr::init_function_context(state, context, scope, _function));
272
2.09M
    if (scope == FunctionContext::FRAGMENT_LOCAL) {
273
790k
        RETURN_IF_ERROR(VExpr::get_const_col(context, nullptr));
274
790k
    }
275
2.09M
    _open_finished = true;
276
2.09M
    return Status::OK();
277
2.09M
}
278
279
2.10M
void VectorizedFnCall::close(VExprContext* context, FunctionContext::FunctionStateScope scope) {
280
2.10M
    VExpr::close_function_context(context, scope, _function);
281
2.10M
    VExpr::close(context, scope);
282
2.10M
}
283
284
12.9k
Status VectorizedFnCall::evaluate_inverted_index(VExprContext* context, uint32_t segment_num_rows) {
285
12.9k
    if (get_num_children() < 1) {
286
        // score() and similar 0-children virtual column functions don't need
287
        // inverted index evaluation; return OK to skip gracefully.
288
89
        return Status::OK();
289
89
    }
290
12.8k
    return _evaluate_inverted_index(context, _function, segment_num_rows);
291
12.9k
}
292
293
36.9k
ZoneMapFilterResult VectorizedFnCall::evaluate_zonemap_filter(const ZoneMapEvalContext& ctx) const {
294
36.9k
    return _function->evaluate_zonemap_filter(ctx, _children);
295
36.9k
}
296
297
123k
bool VectorizedFnCall::can_evaluate_zonemap_filter() const {
298
123k
    return _function != nullptr && !_function->is_blockable() &&
299
123k
           _function->can_evaluate_zonemap_filter(_children);
300
123k
}
301
302
ZoneMapFilterResult VectorizedFnCall::evaluate_dictionary_filter(
303
40.8k
        const DictionaryEvalContext& ctx) const {
304
40.8k
    return _function->evaluate_dictionary_filter(ctx, _children);
305
40.8k
}
306
307
53.4k
bool VectorizedFnCall::can_evaluate_dictionary_filter() const {
308
53.4k
    return _function != nullptr && !_function->is_blockable() &&
309
53.4k
           _function->can_evaluate_dictionary_filter(_children);
310
53.4k
}
311
312
ZoneMapFilterResult VectorizedFnCall::evaluate_bloom_filter(
313
10
        const BloomFilterEvalContext& ctx) const {
314
10
    return _function->evaluate_bloom_filter(ctx, _children);
315
10
}
316
317
7.68k
bool VectorizedFnCall::can_evaluate_bloom_filter() const {
318
7.68k
    return _function != nullptr && !_function->is_blockable() &&
319
7.68k
           _function->can_evaluate_bloom_filter(_children);
320
7.68k
}
321
322
Status VectorizedFnCall::_do_execute(VExprContext* context, const Block* block,
323
                                     const Selector* selector, size_t count,
324
767k
                                     ColumnPtr& result_column, ColumnPtr* arg_column) const {
325
767k
    if (is_const_and_have_executed()) { // const have executed in open function
326
27.8k
        result_column = get_result_from_const(count);
327
27.8k
        return Status::OK();
328
27.8k
    }
329
739k
    if (fast_execute(context, selector, count, result_column)) {
330
649
        return Status::OK();
331
649
    }
332
739k
    DBUG_EXECUTE_IF("VectorizedFnCall.must_in_slow_path", {
333
739k
        if (get_child(0)->is_slot_ref()) {
334
739k
            auto debug_col_name = DebugPoints::instance()->get_debug_param_or_default<std::string>(
335
739k
                    "VectorizedFnCall.must_in_slow_path", "column_name", "");
336
337
739k
            std::vector<std::string> column_names;
338
739k
            boost::split(column_names, debug_col_name, boost::algorithm::is_any_of(","));
339
340
739k
            auto* column_slot_ref = assert_cast<VSlotRef*>(get_child(0).get());
341
739k
            std::string column_name = column_slot_ref->expr_name();
342
739k
            auto it = std::find(column_names.begin(), column_names.end(), column_name);
343
739k
            if (it == column_names.end()) {
344
739k
                return Status::Error<ErrorCode::INTERNAL_ERROR>(
345
739k
                        "column {} should in slow path while VectorizedFnCall::execute.",
346
739k
                        column_name);
347
739k
            }
348
739k
        }
349
739k
    })
350
739k
    DCHECK(_open_finished || block == nullptr) << debug_string();
351
352
739k
    Block temp_block;
353
739k
    ColumnNumbers args(_children.size());
354
355
2.14M
    for (int i = 0; i < _children.size(); ++i) {
356
1.40M
        ColumnPtr tmp_arg_column;
357
1.40M
        RETURN_IF_ERROR(
358
1.40M
                _children[i]->execute_column(context, block, selector, count, tmp_arg_column));
359
1.40M
        auto arg_type = _children[i]->execute_type(block);
360
1.40M
        temp_block.insert({tmp_arg_column, arg_type, _children[i]->expr_name()});
361
1.40M
        args[i] = i;
362
363
1.40M
        if (arg_column != nullptr && i == 0) {
364
61.6k
            *arg_column = tmp_arg_column;
365
61.6k
        }
366
1.40M
    }
367
368
739k
    uint32_t num_columns_without_result = temp_block.columns();
369
    // prepare a column to save result
370
739k
    temp_block.insert({nullptr, _data_type, _expr_name});
371
372
739k
    DBUG_EXECUTE_IF("VectorizedFnCall.wait_before_execute", {
373
739k
        auto possibility = DebugPoints::instance()->get_debug_param_or_default<double>(
374
739k
                "VectorizedFnCall.wait_before_execute", "possibility", 0);
375
739k
        if (random_bool_slow(possibility)) {
376
739k
            LOG(WARNING) << "VectorizedFnCall::execute sleep 30s";
377
739k
            sleep(30);
378
739k
        }
379
739k
    });
380
381
739k
    RETURN_IF_ERROR(_function->execute(context->fn_context(_fn_context_index), temp_block, args,
382
739k
                                       num_columns_without_result, count));
383
738k
    result_column = temp_block.get_by_position(num_columns_without_result).column;
384
738k
    DCHECK_EQ(result_column->size(), count);
385
738k
    RETURN_IF_ERROR(result_column->column_self_check());
386
738k
    return Status::OK();
387
738k
}
388
389
0
size_t VectorizedFnCall::estimate_memory(const size_t rows) {
390
0
    if (is_const_and_have_executed()) { // const have execute in open function
391
0
        return 0;
392
0
    }
393
394
0
    size_t estimate_size = 0;
395
0
    for (auto& child : _children) {
396
0
        estimate_size += child->estimate_memory(rows);
397
0
    }
398
399
0
    if (_data_type->have_maximum_size_of_value()) {
400
0
        estimate_size += rows * _data_type->get_size_of_value_in_memory();
401
0
    } else {
402
0
        estimate_size += rows * 512; /// FIXME: estimated value...
403
0
    }
404
0
    return estimate_size;
405
0
}
406
407
Status VectorizedFnCall::execute_runtime_filter(VExprContext* context, const Block* block,
408
                                                const uint8_t* __restrict filter, size_t count,
409
                                                ColumnPtr& result_column,
410
61.6k
                                                ColumnPtr* arg_column) const {
411
61.6k
    return _do_execute(context, block, nullptr, count, result_column, arg_column);
412
61.6k
}
413
414
Status VectorizedFnCall::execute_column_impl(VExprContext* context, const Block* block,
415
                                             const Selector* selector, size_t count,
416
705k
                                             ColumnPtr& result_column) const {
417
705k
    return _do_execute(context, block, selector, count, result_column, nullptr);
418
705k
}
419
420
bool VectorizedFnCall::can_execute_on_raw_fixed_values(const DataTypePtr& data_type,
421
21
                                                       int column_id) const {
422
21
    if (data_type == nullptr || !raw_comparison_op(_function_name, false).has_value()) {
423
0
        return false;
424
0
    }
425
21
    auto slot_literal = expr_zonemap::extract_slot_and_literal(_children);
426
21
    if (!slot_literal.has_value() || slot_literal->slot_index != column_id ||
427
21
        slot_literal->literal.is_null()) {
428
0
        return false;
429
0
    }
430
21
    const auto raw_type = remove_nullable(data_type);
431
21
    if (!remove_nullable(slot_literal->slot_type)->equals(*raw_type) ||
432
21
        !remove_nullable(slot_literal->literal_type)->equals(*raw_type)) {
433
0
        return false;
434
0
    }
435
21
    const auto primitive_type = raw_type->get_primitive_type();
436
21
    return primitive_type == TYPE_INT || primitive_type == TYPE_BIGINT ||
437
21
           primitive_type == TYPE_FLOAT || primitive_type == TYPE_DOUBLE;
438
21
}
439
440
Status VectorizedFnCall::execute_on_raw_fixed_values(const uint8_t* values, size_t num_values,
441
                                                     size_t value_width,
442
                                                     const DataTypePtr& data_type, int column_id,
443
11
                                                     uint8_t* matches) const {
444
11
    if (!can_execute_on_raw_fixed_values(data_type, column_id)) {
445
0
        return Status::NotSupported("Expression {} cannot evaluate raw fixed-width values",
446
0
                                    expr_name());
447
0
    }
448
11
    DORIS_CHECK(values != nullptr || num_values == 0);
449
11
    DORIS_CHECK(matches != nullptr || num_values == 0);
450
11
    const auto slot_literal = expr_zonemap::extract_slot_and_literal(_children);
451
11
    DORIS_CHECK(slot_literal.has_value());
452
11
    const auto op = raw_comparison_op(_function_name, slot_literal->literal_on_left);
453
11
    DORIS_CHECK(op.has_value());
454
11
    const auto primitive_type = remove_nullable(data_type)->get_primitive_type();
455
11
    const size_t expected_width = primitive_type == TYPE_INT || primitive_type == TYPE_FLOAT
456
11
                                          ? sizeof(uint32_t)
457
11
                                          : sizeof(uint64_t);
458
11
    if (value_width != expected_width) {
459
0
        return Status::Corruption("Raw expression width {} does not match expected {}", value_width,
460
0
                                  expected_width);
461
0
    }
462
11
    switch (primitive_type) {
463
9
    case TYPE_INT:
464
9
        execute_raw_comparison<int32_t, TYPE_INT>(values, num_values, slot_literal->literal, *op,
465
9
                                                  matches);
466
9
        break;
467
0
    case TYPE_BIGINT:
468
0
        execute_raw_comparison<int64_t, TYPE_BIGINT>(values, num_values, slot_literal->literal, *op,
469
0
                                                     matches);
470
0
        break;
471
2
    case TYPE_FLOAT:
472
2
        execute_raw_comparison<float, TYPE_FLOAT>(values, num_values, slot_literal->literal, *op,
473
2
                                                  matches);
474
2
        break;
475
0
    case TYPE_DOUBLE:
476
0
        execute_raw_comparison<double, TYPE_DOUBLE>(values, num_values, slot_literal->literal, *op,
477
0
                                                    matches);
478
0
        break;
479
0
    default:
480
0
        __builtin_unreachable();
481
11
    }
482
11
    return Status::OK();
483
11
}
484
485
380k
const std::string& VectorizedFnCall::expr_name() const {
486
380k
    return _expr_name;
487
380k
}
488
489
92
std::string VectorizedFnCall::function_name() const {
490
92
    return _function_name;
491
92
}
492
493
524
std::string VectorizedFnCall::debug_string() const {
494
524
    std::stringstream out;
495
524
    out << "VectorizedFn[";
496
524
    out << _expr_name;
497
524
    out << "]{";
498
524
    bool first = true;
499
1.04k
    for (const auto& input_expr : children()) {
500
1.04k
        if (first) {
501
524
            first = false;
502
524
        } else {
503
523
            out << ",";
504
523
        }
505
1.04k
        out << "\n" << input_expr->debug_string();
506
1.04k
    }
507
524
    out << "}";
508
524
    return out.str();
509
524
}
510
511
0
std::string VectorizedFnCall::debug_string(const std::vector<VectorizedFnCall*>& agg_fns) {
512
0
    std::stringstream out;
513
0
    out << "[";
514
0
    for (int i = 0; i < agg_fns.size(); ++i) {
515
0
        out << (i == 0 ? "" : " ") << agg_fns[i]->debug_string();
516
0
    }
517
0
    out << "]";
518
0
    return out.str();
519
0
}
520
521
2.66k
bool VectorizedFnCall::can_push_down_to_index() const {
522
2.66k
    return _function->can_push_down_to_index();
523
2.66k
}
524
525
98.2k
bool VectorizedFnCall::is_deterministic() const {
526
98.2k
    static const std::set<std::string> NON_DETERMINISTIC_FUNCTIONS = {
527
98.2k
            "random", "rand", "random_bytes", "uuid", "uuid_numeric"};
528
98.3k
    return !NON_DETERMINISTIC_FUNCTIONS.contains(_function_name) && VExpr::is_deterministic();
529
98.2k
}
530
531
60.7k
bool VectorizedFnCall::is_safe_to_execute_on_selected_rows() const {
532
60.7k
    static const std::set<std::string> TOTAL_PREDICATE_FUNCTIONS = {
533
60.7k
            "eq", "ne", "lt", "le", "gt", "ge", "in", "not_in", "is_null_pred", "is_not_null_pred"};
534
    // Selected-row execution may hide data-dependent errors in rows rejected by an earlier
535
    // predicate. Keep function calls unsafe by default and opt in only operations that are total
536
    // for their input domain; child checks then reject expressions such as gt(mod(x, -1), 0).
537
60.7k
    return TOTAL_PREDICATE_FUNCTIONS.contains(_function_name) &&
538
60.7k
           VExpr::is_safe_to_execute_on_selected_rows();
539
60.7k
}
540
541
0
bool VectorizedFnCall::equals(const VExpr& other) {
542
0
    const auto* other_ptr = dynamic_cast<const VectorizedFnCall*>(&other);
543
0
    if (!other_ptr) {
544
0
        return false;
545
0
    }
546
0
    if (this->_function_name != other_ptr->_function_name) {
547
0
        return false;
548
0
    }
549
0
    if (get_num_children() != other_ptr->get_num_children()) {
550
0
        return false;
551
0
    }
552
0
    for (uint16_t i = 0; i < get_num_children(); i++) {
553
0
        if (!this->get_child(i)->equals(*other_ptr->get_child(i))) {
554
0
            return false;
555
0
        }
556
0
    }
557
0
    return true;
558
0
}
559
560
/*
561
 * For ANN range search we expect a comparison expression (LE/LT/GE/GT) whose left side is either:
562
 *   1) a vector distance function call, or
563
 *   2) a cast/virtual slot that unwraps to the function call when the planner promotes float to
564
 *      double literals.
565
 *
566
 * Visually the logical tree looks like:
567
 *
568
 *   FunctionCall(LE/LT/GE/GT)
569
 *   |----------------
570
 *   |               |
571
 *   |               |
572
 *   VirtualSlotRef* Float32Literal/Float64Literal
573
 *   |
574
 *   |
575
 *   Cast(Float -> Double)*
576
 *   |
577
 *   FunctionCall(distance)
578
 *   |----------------
579
 *   |               |
580
 *   |               |
581
 *   SlotRef         ArrayLiteral/Cast(String as Array<FLOAT>)
582
 *
583
 * Items marked with * are optional and depend on literal types/virtual column usage. The helper
584
 * below normalizes the shape and validates distance function, slot, and constant vector inputs.
585
 */
586
587
void VectorizedFnCall::prepare_ann_range_search(
588
        const doris::VectorSearchUserParams& user_params,
589
15.0k
        segment_v2::AnnRangeSearchRuntime& range_search_runtime, bool& suitable_for_ann_index) {
590
15.0k
    if (!suitable_for_ann_index) {
591
0
        return;
592
0
    }
593
594
15.0k
    if (OPS_FOR_ANN_RANGE_SEARCH.find(this->op()) == OPS_FOR_ANN_RANGE_SEARCH.end()) {
595
11.1k
        suitable_for_ann_index = false;
596
11.1k
        return;
597
11.1k
    }
598
599
3.88k
    auto mark_unsuitable = [&](const std::string& reason) {
600
3.83k
        suitable_for_ann_index = false;
601
18.4E
        VLOG_DEBUG << "ANN range search skipped: " << reason;
602
3.83k
    };
603
604
3.88k
    range_search_runtime.is_le_or_lt =
605
3.88k
            (this->op() == TExprOpcode::LE || this->op() == TExprOpcode::LT);
606
607
3.88k
    DCHECK(_children.size() == 2);
608
609
3.88k
    auto left_child = get_child(0);
610
3.88k
    auto right_child = get_child(1);
611
612
    // ========== Step 1: Check left child - must be a distance function ==========
613
3.88k
    auto get_virtual_expr = [&](const VExprSPtr& expr,
614
5.03k
                                std::shared_ptr<VirtualSlotRef>& slot_ref) -> VExprSPtr {
615
5.03k
        auto virtual_ref = std::dynamic_pointer_cast<VirtualSlotRef>(expr);
616
5.03k
        if (virtual_ref != nullptr) {
617
119
            DCHECK(virtual_ref->get_virtual_column_expr() != nullptr);
618
119
            slot_ref = virtual_ref;
619
119
            return virtual_ref->get_virtual_column_expr();
620
119
        }
621
4.92k
        return expr;
622
5.03k
    };
623
624
3.88k
    std::shared_ptr<VirtualSlotRef> vir_slot_ref;
625
3.88k
    auto normalized_left = get_virtual_expr(left_child, vir_slot_ref);
626
627
    // Try to find the distance function call, it may be wrapped in a Cast(Float->Double)
628
3.88k
    std::shared_ptr<VectorizedFnCall> function_call =
629
3.88k
            std::dynamic_pointer_cast<VectorizedFnCall>(normalized_left);
630
3.88k
    bool has_float_to_double_cast = false;
631
632
3.88k
    if (function_call == nullptr) {
633
        // Check if it's a Cast expression wrapping a function call
634
1.40k
        auto cast_expr = std::dynamic_pointer_cast<VCastExpr>(normalized_left);
635
1.40k
        if (cast_expr == nullptr) {
636
241
            mark_unsuitable("Left child is neither a function call nor a cast expression.");
637
241
            return;
638
241
        }
639
1.16k
        has_float_to_double_cast = true;
640
1.16k
        auto normalized_cast_child = get_virtual_expr(cast_expr->get_child(0), vir_slot_ref);
641
1.16k
        function_call = std::dynamic_pointer_cast<VectorizedFnCall>(normalized_cast_child);
642
1.16k
        if (function_call == nullptr) {
643
1.12k
            mark_unsuitable("Left child of cast is not a function call.");
644
1.12k
            return;
645
1.12k
        }
646
1.16k
    }
647
648
    // Check if it's a supported distance function
649
2.51k
    if (DISTANCE_FUNCS.find(function_call->_function_name) == DISTANCE_FUNCS.end()) {
650
2.46k
        mark_unsuitable(fmt::format("Left child is not a supported distance function: {}",
651
2.46k
                                    function_call->_function_name));
652
2.46k
        return;
653
2.46k
    }
654
655
    // Strip the _approximate suffix to get metric type
656
51
    std::string metric_name = function_call->_function_name;
657
51
    metric_name = metric_name.substr(0, metric_name.size() - 12);
658
51
    range_search_runtime.metric_type = segment_v2::string_to_metric(metric_name);
659
660
    // ========== Step 2: Validate distance function arguments ==========
661
    // Identify the slot ref child and the constant query array child (ArrayLiteral or CAST to array)
662
51
    Int32 idx_of_slot_ref = -1;
663
51
    Int32 idx_of_array_expr = -1;
664
94
    auto classify_child = [&](const VExprSPtr& child, UInt16 index) {
665
94
        if (idx_of_slot_ref == -1 && std::dynamic_pointer_cast<VSlotRef>(child) != nullptr) {
666
47
            idx_of_slot_ref = index;
667
47
            return;
668
47
        }
669
47
        if (idx_of_array_expr == -1 &&
670
47
            (std::dynamic_pointer_cast<VArrayLiteral>(child) != nullptr ||
671
47
             std::dynamic_pointer_cast<VCastExpr>(child) != nullptr)) {
672
40
            idx_of_array_expr = index;
673
40
        }
674
47
    };
675
676
145
    for (UInt16 i = 0; i < function_call->get_num_children(); ++i) {
677
94
        classify_child(function_call->get_child(i), i);
678
94
    }
679
680
51
    if (idx_of_slot_ref == -1 || idx_of_array_expr == -1) {
681
7
        mark_unsuitable("slot ref or array literal/cast is missing.");
682
7
        return;
683
7
    }
684
685
44
    auto slot_ref = std::dynamic_pointer_cast<VSlotRef>(
686
44
            function_call->get_child(static_cast<UInt16>(idx_of_slot_ref)));
687
44
    range_search_runtime.src_col_idx = slot_ref->column_id();
688
44
    range_search_runtime.dst_col_idx = vir_slot_ref == nullptr ? -1 : vir_slot_ref->column_id();
689
690
    // Materialize the constant array expression and validate its shape and types
691
44
    auto array_expr = function_call->get_child(static_cast<UInt16>(idx_of_array_expr));
692
44
    auto extract_result = extract_query_vector(array_expr);
693
44
    if (!extract_result.has_value()) {
694
0
        mark_unsuitable("Failed to extract query vector from constant array expression.");
695
0
        return;
696
0
    }
697
44
    range_search_runtime.query_value = extract_result.value();
698
44
    range_search_runtime.dim = range_search_runtime.query_value->size();
699
700
    // ========== Step 3: Check right child - must be a float/double literal ==========
701
44
    auto right_literal = std::dynamic_pointer_cast<VLiteral>(right_child);
702
44
    if (right_literal == nullptr) {
703
1
        mark_unsuitable("Right child is not a literal.");
704
1
        return;
705
1
    }
706
707
    // Handle nullable literal gracefully - just mark as unsuitable instead of crash
708
43
    if (right_literal->is_nullable()) {
709
0
        mark_unsuitable("Right literal is nullable, not supported for ANN range search.");
710
0
        return;
711
0
    }
712
713
43
    auto right_type = right_literal->get_data_type();
714
43
    PrimitiveType right_primitive = right_type->get_primitive_type();
715
43
    const bool float32_literal = right_primitive == PrimitiveType::TYPE_FLOAT;
716
43
    const bool float64_literal = right_primitive == PrimitiveType::TYPE_DOUBLE;
717
718
43
    if (!float32_literal && !float64_literal) {
719
0
        mark_unsuitable("Right child is not a Float32Literal or Float64Literal.");
720
0
        return;
721
0
    }
722
723
    // Validate consistency: if we have Cast(Float->Double), right must be double literal
724
43
    if (has_float_to_double_cast && !float64_literal) {
725
0
        mark_unsuitable("Cast expression expects double literal on right side.");
726
0
        return;
727
0
    }
728
729
    // Extract radius value
730
43
    auto right_col = right_literal->get_column_ptr()->convert_to_full_column_if_const();
731
43
    if (float32_literal) {
732
7
        const ColumnFloat32* cf32_right = assert_cast<const ColumnFloat32*>(right_col.get());
733
7
        range_search_runtime.radius = cf32_right->get_data()[0];
734
36
    } else {
735
36
        const ColumnFloat64* cf64_right = assert_cast<const ColumnFloat64*>(right_col.get());
736
36
        range_search_runtime.radius = static_cast<float>(cf64_right->get_data()[0]);
737
36
    }
738
739
    // ========== Done: Mark as suitable for ANN range search ==========
740
43
    range_search_runtime.is_ann_range_search = true;
741
43
    range_search_runtime.user_params = user_params;
742
43
    VLOG_DEBUG << fmt::format("Ann range search params: {}", range_search_runtime.to_string());
743
43
    return;
744
43
}
745
746
Status VectorizedFnCall::evaluate_ann_range_search(
747
        const segment_v2::AnnRangeSearchRuntime& range_search_runtime,
748
        const std::vector<std::unique_ptr<segment_v2::IndexIterator>>& cid_to_index_iterators,
749
        const std::vector<ColumnId>& idx_to_cid,
750
        const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>& column_iterators,
751
        size_t rows_of_segment, roaring::Roaring& row_bitmap,
752
        segment_v2::AnnIndexStats& ann_index_stats, bool enable_result_cache,
753
11.7k
        AnnRangeSearchEvaluationResult& evaluation_result) {
754
11.7k
    evaluation_result = {};
755
11.7k
    if (range_search_runtime.is_ann_range_search == false) {
756
11.7k
        return Status::OK();
757
11.7k
    }
758
759
18.4E
    VLOG_DEBUG << fmt::format("Try apply ann range search. Local search params: {}",
760
18.4E
                              range_search_runtime.to_string());
761
33
    size_t origin_num = row_bitmap.cardinality();
762
763
33
    const auto idx_in_block = range_search_runtime.src_col_idx;
764
33
    DCHECK_LT(idx_in_block, idx_to_cid.size())
765
0
            << "idx_in_block: " << idx_in_block << ", idx_to_cid.size(): " << idx_to_cid.size();
766
767
33
    ColumnId src_col_cid = idx_to_cid[idx_in_block];
768
33
    DCHECK(src_col_cid < cid_to_index_iterators.size());
769
33
    segment_v2::IndexIterator* index_iterator = cid_to_index_iterators[src_col_cid].get();
770
33
    if (index_iterator == nullptr) {
771
1
        VLOG_DEBUG << "ANN range search skipped: "
772
0
                   << fmt::format("No index iterator for column cid {}", src_col_cid);
773
1
        ;
774
1
        return Status::OK();
775
1
    }
776
777
32
    segment_v2::AnnIndexIterator* ann_index_iterator =
778
32
            dynamic_cast<segment_v2::AnnIndexIterator*>(index_iterator);
779
32
    if (ann_index_iterator == nullptr) {
780
0
        VLOG_DEBUG << "ANN range search skipped: "
781
0
                   << fmt::format("Column cid {} has no ANN index iterator", src_col_cid);
782
0
        return Status::OK();
783
0
    }
784
18.4E
    DCHECK(ann_index_iterator->get_reader(AnnIndexReaderType::ANN) != nullptr)
785
18.4E
            << "Ann index iterator should have reader. Column cid: " << src_col_cid;
786
32
    std::shared_ptr<AnnIndexReader> ann_index_reader = std::dynamic_pointer_cast<AnnIndexReader>(
787
32
            ann_index_iterator->get_reader(segment_v2::AnnIndexReaderType::ANN));
788
18.4E
    DCHECK(ann_index_reader != nullptr)
789
18.4E
            << "Ann index reader should not be null. Column cid: " << src_col_cid;
790
    // Check if metrics type is match.
791
32
    if (ann_index_reader->get_metric_type() != range_search_runtime.metric_type) {
792
0
        VLOG_DEBUG << "ANN range search skipped: "
793
0
                   << fmt::format("Metric type mismatch. Index={} Query={}",
794
0
                                  segment_v2::metric_to_string(ann_index_reader->get_metric_type()),
795
0
                                  segment_v2::metric_to_string(range_search_runtime.metric_type));
796
0
        return Status::OK();
797
0
    }
798
799
    // Check dimension if available (>0)
800
32
    const size_t index_dim = ann_index_reader->get_dimension();
801
38
    if (index_dim > 0 && index_dim != range_search_runtime.dim) {
802
8
        return Status::InvalidArgument(
803
8
                "Ann range search query dimension {} does not match index dimension {}",
804
8
                range_search_runtime.dim, index_dim);
805
8
    }
806
807
24
    const auto& user_params = range_search_runtime.user_params;
808
24
    if (user_params.should_fallback_ann_index_by_small_candidate(origin_num, rows_of_segment)) {
809
0
        VLOG_DEBUG << fmt::format(
810
0
                "Ann range search input rows {} reach small candidate threshold, "
811
0
                "rows_of_segment: {}, absolute_threshold: {}, percent_threshold: {}, "
812
0
                "will not use ann index to filter",
813
0
                origin_num, rows_of_segment, user_params.ann_index_candidate_rows_threshold,
814
0
                user_params.ann_index_candidate_rows_percent_threshold);
815
0
        ann_index_stats.fall_back_brute_force_cnt += 1;
816
0
        ann_index_stats.range_fallback_by_small_candidate_cnt += 1;
817
0
        ann_index_stats.range_fallback_small_candidate_rows += origin_num;
818
0
        return Status::OK();
819
0
    }
820
821
24
    auto stats = std::make_unique<segment_v2::AnnIndexStats>();
822
    // Track load index timing
823
24
    {
824
24
        SCOPED_TIMER(&(stats->load_index_costs_ns));
825
24
        if (!ann_index_iterator->try_load_index()) {
826
2
            VLOG_DEBUG << "ANN range search skipped: "
827
0
                       << fmt::format("Failed to load ANN index for column cid {}", src_col_cid);
828
2
            ann_index_stats.fall_back_brute_force_cnt += 1;
829
2
            return Status::OK();
830
2
        }
831
22
        double load_costs_ms = static_cast<double>(stats->load_index_costs_ns.value()) / 1000000.0;
832
22
        DorisMetrics::instance()->ann_index_load_costs_ms->increment(
833
22
                static_cast<int64_t>(load_costs_ms));
834
22
    }
835
836
0
    AnnRangeSearchParams params = range_search_runtime.to_range_search_params();
837
838
22
    params.roaring = &row_bitmap;
839
22
    params.enable_result_cache = enable_result_cache;
840
22
    DCHECK(params.roaring != nullptr);
841
22
    DCHECK(params.query_value != nullptr);
842
22
    segment_v2::AnnRangeSearchResult result;
843
22
    RETURN_IF_ERROR(ann_index_iterator->range_search(params, range_search_runtime.user_params,
844
22
                                                     &result, stats.get()));
845
846
22
#ifndef NDEBUG
847
22
    if (range_search_runtime.is_le_or_lt == false &&
848
22
        ann_index_reader->get_metric_type() == AnnIndexMetric::L2) {
849
7
        DCHECK(result.distance == nullptr) << "Should not have distance";
850
7
    }
851
22
    if (range_search_runtime.is_le_or_lt == true &&
852
22
        ann_index_reader->get_metric_type() == AnnIndexMetric::IP) {
853
4
        DCHECK(result.distance == nullptr);
854
4
    }
855
22
#endif
856
22
    DCHECK(result.roaring != nullptr);
857
22
    row_bitmap = *result.roaring;
858
859
    // Process virtual column
860
22
    bool dist_fulfilled = false;
861
22
    if (range_search_runtime.dst_col_idx >= 0) {
862
        // Prepare materialization if we can use result from index.
863
        // Typical situation: range search and operator is LE or LT.
864
4
        if (result.distance != nullptr) {
865
2
            DCHECK(result.row_ids != nullptr);
866
2
            ColumnId dst_col_cid = idx_to_cid[range_search_runtime.dst_col_idx];
867
2
            DCHECK(dst_col_cid < column_iterators.size());
868
2
            DCHECK(column_iterators[dst_col_cid] != nullptr);
869
2
            segment_v2::ColumnIterator* column_iterator = column_iterators[dst_col_cid].get();
870
2
            DCHECK(column_iterator != nullptr);
871
2
            segment_v2::VirtualColumnIterator* virtual_column_iterator =
872
2
                    dynamic_cast<segment_v2::VirtualColumnIterator*>(column_iterator);
873
2
            DCHECK(virtual_column_iterator != nullptr);
874
            // Now convert distance to column
875
2
            size_t size = result.roaring->cardinality();
876
2
            auto distance_col = ColumnFloat32::create(size);
877
2
            const float* src = result.distance.get();
878
2
            float* dst = distance_col->get_data().data();
879
15
            for (size_t i = 0; i < size; ++i) {
880
13
                dst[i] = src[i];
881
13
            }
882
2
            virtual_column_iterator->prepare_materialization(std::move(distance_col),
883
2
                                                             std::move(result.row_ids));
884
2
            dist_fulfilled = true;
885
2
        } else {
886
            // Whether the ANN index should have produced distance depends on metric and operator:
887
            //  - L2: distance is produced for LE/LT; not produced for GE/GT
888
            //  - IP: distance is produced for GE/GT; not produced for LE/LT
889
2
#ifndef NDEBUG
890
2
            const bool should_have_distance =
891
2
                    (range_search_runtime.is_le_or_lt &&
892
2
                     range_search_runtime.metric_type == AnnIndexMetric::L2) ||
893
2
                    (!range_search_runtime.is_le_or_lt &&
894
2
                     range_search_runtime.metric_type == AnnIndexMetric::IP);
895
            // If we expected distance but didn't get it, assert in debug to catch logic errors.
896
2
            DCHECK(!should_have_distance) << "Expected distance from ANN index but got none";
897
2
#endif
898
2
        }
899
18
    } else {
900
        // Dest is not virtual column.
901
18
        dist_fulfilled = true;
902
18
    }
903
904
22
    evaluation_result.executed = true;
905
22
    evaluation_result.dist_fulfilled = dist_fulfilled;
906
18.4E
    VLOG_DEBUG << fmt::format(
907
18.4E
            "Ann range search filtered {} rows, origin {} rows, virtual column is full-filled: {}",
908
18.4E
            origin_num - row_bitmap.cardinality(), origin_num, dist_fulfilled);
909
910
22
    ann_index_stats = *stats;
911
22
    return Status::OK();
912
22
}
913
914
1.26M
double VectorizedFnCall::execute_cost() const {
915
1.26M
    if (!_function) {
916
0
        throw Exception(
917
0
                Status::InternalError("Function is null in expression: {}", this->debug_string()));
918
0
    }
919
1.26M
    double cost = _function->execute_cost();
920
2.52M
    for (const auto& child : _children) {
921
2.52M
        cost += child->execute_cost();
922
2.52M
    }
923
1.26M
    return cost;
924
1.26M
}
925
926
} // namespace doris