Coverage Report

Created: 2026-07-10 10:49

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 <ostream>
28
#include <set>
29
30
#include "common/config.h"
31
#include "common/exception.h"
32
#include "common/logging.h"
33
#include "common/status.h"
34
#include "common/utils.h"
35
#include "core/assert_cast.h"
36
#include "core/block/block.h"
37
#include "core/block/column_numbers.h"
38
#include "core/column/column.h"
39
#include "core/column/column_array.h"
40
#include "core/column/column_nullable.h"
41
#include "core/column/column_vector.h"
42
#include "core/data_type/data_type.h"
43
#include "core/data_type/data_type_agg_state.h"
44
#include "core/types.h"
45
#include "exec/common/util.hpp"
46
#include "exec/pipeline/pipeline_task.h"
47
#include "exprs/function/array/function_array_distance.h"
48
#include "exprs/function/function_agg_state.h"
49
#include "exprs/function/function_fake.h"
50
#include "exprs/function/function_java_udf.h"
51
#include "exprs/function/function_python_udf.h"
52
#include "exprs/function/function_rpc.h"
53
#include "exprs/function/simple_function_factory.h"
54
#include "exprs/function_context.h"
55
#include "exprs/varray_literal.h"
56
#include "exprs/vcast_expr.h"
57
#include "exprs/vexpr_context.h"
58
#include "exprs/virtual_slot_ref.h"
59
#include "exprs/vliteral.h"
60
#include "runtime/runtime_state.h"
61
#include "storage/index/ann/ann_index.h"
62
#include "storage/index/ann/ann_index_iterator.h"
63
#include "storage/index/ann/ann_search_params.h"
64
#include "storage/index/index_reader.h"
65
#include "storage/index/zone_map/zonemap_eval_context.h"
66
#include "storage/segment/column_reader.h"
67
#include "storage/segment/virtual_column_iterator.h"
68
69
namespace doris {
70
class RowDescriptor;
71
class RuntimeState;
72
class TExprNode;
73
} // namespace doris
74
75
namespace doris {
76
77
const std::string AGG_STATE_SUFFIX = "_state";
78
79
// Now left child is a function call, we need to check if it is a distance function
80
const static std::set<std::string> DISTANCE_FUNCS = {L2DistanceApproximate::name,
81
                                                     InnerProductApproximate::name};
82
const static std::set<TExprOpcode::type> OPS_FOR_ANN_RANGE_SEARCH = {
83
        TExprOpcode::GE, TExprOpcode::LE, TExprOpcode::LE, TExprOpcode::GT, TExprOpcode::LT};
84
85
623k
VectorizedFnCall::VectorizedFnCall(const TExprNode& node) : VExpr(node) {
86
623k
    _function_name = _fn.name.function_name;
87
623k
}
88
89
Status VectorizedFnCall::prepare(RuntimeState* state, const RowDescriptor& desc,
90
655k
                                 VExprContext* context) {
91
655k
    RETURN_IF_ERROR_OR_PREPARED(VExpr::prepare(state, desc, context));
92
655k
    ColumnsWithTypeAndName argument_template;
93
655k
    argument_template.reserve(_children.size());
94
1.28M
    for (auto child : _children) {
95
1.28M
        if (child->is_literal()) {
96
            // For some functions, he needs some literal columns to derive the return type.
97
605k
            auto literal_node = std::dynamic_pointer_cast<VLiteral>(child);
98
605k
            argument_template.emplace_back(literal_node->get_column_ptr(), child->data_type(),
99
605k
                                           child->expr_name());
100
674k
        } else {
101
674k
            argument_template.emplace_back(nullptr, child->data_type(), child->expr_name());
102
674k
        }
103
1.28M
    }
104
105
655k
    _expr_name = fmt::format("VectorizedFnCall[{}](arguments={},return={})", _fn.name.function_name,
106
655k
                             get_child_names(), _data_type->get_name());
107
655k
    if (_fn.binary_type == TFunctionBinaryType::RPC) {
108
0
        _function = FunctionRPC::create(_fn, argument_template, _data_type);
109
655k
    } else if (_fn.binary_type == TFunctionBinaryType::JAVA_UDF) {
110
527
        if (config::enable_java_support) {
111
527
            if (_fn.is_udtf_function) {
112
                // fake function. it's no use and can't execute.
113
56
                auto builder =
114
56
                        std::make_shared<DefaultFunctionBuilder>(FunctionFake<UDTFImpl>::create());
115
56
                _function = builder->build(argument_template, std::make_shared<DataTypeUInt8>());
116
471
            } else {
117
471
                _function = JavaFunctionCall::create(_fn, argument_template, _data_type);
118
471
            }
119
527
        } else {
120
0
            return Status::InternalError(
121
0
                    "Java UDF is not enabled, you can change be config enable_java_support to true "
122
0
                    "and restart be.");
123
0
        }
124
654k
    } else if (_fn.binary_type == TFunctionBinaryType::PYTHON_UDF) {
125
670
        if (config::enable_python_udf_support) {
126
670
            if (_fn.is_udtf_function) {
127
                // fake function. it's no use and can't execute.
128
                // Python UDTF is executed via PythonUDTFFunction in table function path
129
286
                auto builder =
130
286
                        std::make_shared<DefaultFunctionBuilder>(FunctionFake<UDTFImpl>::create());
131
286
                _function = builder->build(argument_template, std::make_shared<DataTypeUInt8>());
132
384
            } else {
133
384
                _function = PythonFunctionCall::create(_fn, argument_template, _data_type);
134
384
                LOG(INFO) << fmt::format(
135
384
                        "create python function call: {}, runtime version: {}, function code: {}",
136
384
                        _fn.name.function_name, _fn.runtime_version, _fn.function_code);
137
384
            }
138
670
        } else {
139
0
            return Status::InternalError(
140
0
                    "Python UDF is not enabled, you can change be config enable_python_udf_support "
141
0
                    "to true and restart be.");
142
0
        }
143
654k
    } else if (_fn.binary_type == TFunctionBinaryType::AGG_STATE) {
144
751
        DataTypes argument_types;
145
1.10k
        for (auto column : argument_template) {
146
1.10k
            argument_types.emplace_back(column.type);
147
1.10k
        }
148
149
751
        if (match_suffix(_fn.name.function_name, AGG_STATE_SUFFIX)) {
150
751
            if (_data_type->is_nullable()) {
151
0
                return Status::InternalError("State function's return type must be not nullable");
152
0
            }
153
751
            if (_data_type->get_primitive_type() != PrimitiveType::TYPE_AGG_STATE) {
154
0
                return Status::InternalError(
155
0
                        "State function's return type must be agg_state but get {}",
156
0
                        _data_type->get_family_name());
157
0
            }
158
751
            _function = FunctionAggState::create(
159
751
                    argument_types, _data_type,
160
751
                    assert_cast<const DataTypeAggState*>(_data_type.get())->get_nested_function());
161
751
        } else {
162
0
            return Status::InternalError("Function {} is not endwith '_state'", _fn.signature);
163
0
        }
164
653k
    } else {
165
        // get the function. won't prepare function.
166
653k
        _function = SimpleFunctionFactory::instance().get_function(
167
653k
                _fn.name.function_name, argument_template, _data_type,
168
653k
                {.new_version_unix_timestamp = state->query_options().new_version_unix_timestamp},
169
653k
                state->be_exec_version());
170
653k
    }
171
655k
    if (_function == nullptr) {
172
2
        return Status::InternalError("Could not find function {}, arg {} return {} ",
173
2
                                     _fn.name.function_name, get_child_type_names(),
174
2
                                     _data_type->get_name());
175
2
    }
176
655k
    VExpr::register_function_context(state, context);
177
655k
    _function_name = _fn.name.function_name;
178
655k
    _prepare_finished = true;
179
180
655k
    FunctionContext* fn_ctx = context->fn_context(_fn_context_index);
181
655k
    if (fn().__isset.dict_function) {
182
95
        fn_ctx->set_dict_function(fn().dict_function);
183
95
    }
184
655k
    return Status::OK();
185
655k
}
186
187
Status VectorizedFnCall::open(RuntimeState* state, VExprContext* context,
188
1.82M
                              FunctionContext::FunctionStateScope scope) {
189
1.82M
    DCHECK(_prepare_finished);
190
3.45M
    for (auto& i : _children) {
191
3.45M
        RETURN_IF_ERROR(i->open(state, context, scope));
192
3.45M
    }
193
1.82M
    RETURN_IF_ERROR(VExpr::init_function_context(state, context, scope, _function));
194
1.82M
    if (scope == FunctionContext::FRAGMENT_LOCAL) {
195
655k
        RETURN_IF_ERROR(VExpr::get_const_col(context, nullptr));
196
655k
    }
197
1.82M
    _open_finished = true;
198
1.82M
    return Status::OK();
199
1.82M
}
200
201
1.82M
void VectorizedFnCall::close(VExprContext* context, FunctionContext::FunctionStateScope scope) {
202
1.82M
    VExpr::close_function_context(context, scope, _function);
203
1.82M
    VExpr::close(context, scope);
204
1.82M
}
205
206
12.0k
Status VectorizedFnCall::evaluate_inverted_index(VExprContext* context, uint32_t segment_num_rows) {
207
12.0k
    if (get_num_children() < 1) {
208
        // score() and similar 0-children virtual column functions don't need
209
        // inverted index evaluation; return OK to skip gracefully.
210
63
        return Status::OK();
211
63
    }
212
11.9k
    return _evaluate_inverted_index(context, _function, segment_num_rows);
213
12.0k
}
214
215
23.3k
ZoneMapFilterResult VectorizedFnCall::evaluate_zonemap_filter(const ZoneMapEvalContext& ctx) const {
216
23.3k
    return _function->evaluate_zonemap_filter(ctx, _children);
217
23.3k
}
218
219
79.9k
bool VectorizedFnCall::can_evaluate_zonemap_filter() const {
220
79.9k
    return _function != nullptr && !_function->is_blockable() &&
221
79.9k
           _function->can_evaluate_zonemap_filter(_children);
222
79.9k
}
223
224
ZoneMapFilterResult VectorizedFnCall::evaluate_dictionary_filter(
225
20.4k
        const DictionaryEvalContext& ctx) const {
226
20.4k
    return _function->evaluate_dictionary_filter(ctx, _children);
227
20.4k
}
228
229
30.2k
bool VectorizedFnCall::can_evaluate_dictionary_filter() const {
230
30.2k
    return _function != nullptr && !_function->is_blockable() &&
231
30.2k
           _function->can_evaluate_dictionary_filter(_children);
232
30.2k
}
233
234
ZoneMapFilterResult VectorizedFnCall::evaluate_bloom_filter(
235
5
        const BloomFilterEvalContext& ctx) const {
236
5
    return _function->evaluate_bloom_filter(ctx, _children);
237
5
}
238
239
4.89k
bool VectorizedFnCall::can_evaluate_bloom_filter() const {
240
4.89k
    return _function != nullptr && !_function->is_blockable() &&
241
4.89k
           _function->can_evaluate_bloom_filter(_children);
242
4.89k
}
243
244
Status VectorizedFnCall::_do_execute(VExprContext* context, const Block* block,
245
                                     const Selector* selector, size_t count,
246
645k
                                     ColumnPtr& result_column, ColumnPtr* arg_column) const {
247
645k
    if (is_const_and_have_executed()) { // const have executed in open function
248
24.1k
        result_column = get_result_from_const(count);
249
24.1k
        return Status::OK();
250
24.1k
    }
251
621k
    if (fast_execute(context, selector, count, result_column)) {
252
642
        return Status::OK();
253
642
    }
254
620k
    DBUG_EXECUTE_IF("VectorizedFnCall.must_in_slow_path", {
255
620k
        if (get_child(0)->is_slot_ref()) {
256
620k
            auto debug_col_name = DebugPoints::instance()->get_debug_param_or_default<std::string>(
257
620k
                    "VectorizedFnCall.must_in_slow_path", "column_name", "");
258
259
620k
            std::vector<std::string> column_names;
260
620k
            boost::split(column_names, debug_col_name, boost::algorithm::is_any_of(","));
261
262
620k
            auto* column_slot_ref = assert_cast<VSlotRef*>(get_child(0).get());
263
620k
            std::string column_name = column_slot_ref->expr_name();
264
620k
            auto it = std::find(column_names.begin(), column_names.end(), column_name);
265
620k
            if (it == column_names.end()) {
266
620k
                return Status::Error<ErrorCode::INTERNAL_ERROR>(
267
620k
                        "column {} should in slow path while VectorizedFnCall::execute.",
268
620k
                        column_name);
269
620k
            }
270
620k
        }
271
620k
    })
272
620k
    DCHECK(_open_finished || block == nullptr) << debug_string();
273
274
620k
    Block temp_block;
275
620k
    ColumnNumbers args(_children.size());
276
277
1.81M
    for (int i = 0; i < _children.size(); ++i) {
278
1.19M
        ColumnPtr tmp_arg_column;
279
1.19M
        RETURN_IF_ERROR(
280
1.19M
                _children[i]->execute_column(context, block, selector, count, tmp_arg_column));
281
1.19M
        auto arg_type = _children[i]->execute_type(block);
282
1.19M
        temp_block.insert({tmp_arg_column, arg_type, _children[i]->expr_name()});
283
1.19M
        args[i] = i;
284
285
1.19M
        if (arg_column != nullptr && i == 0) {
286
17.3k
            *arg_column = tmp_arg_column;
287
17.3k
        }
288
1.19M
    }
289
290
620k
    uint32_t num_columns_without_result = temp_block.columns();
291
    // prepare a column to save result
292
620k
    temp_block.insert({nullptr, _data_type, _expr_name});
293
294
620k
    DBUG_EXECUTE_IF("VectorizedFnCall.wait_before_execute", {
295
620k
        auto possibility = DebugPoints::instance()->get_debug_param_or_default<double>(
296
620k
                "VectorizedFnCall.wait_before_execute", "possibility", 0);
297
620k
        if (random_bool_slow(possibility)) {
298
620k
            LOG(WARNING) << "VectorizedFnCall::execute sleep 30s";
299
620k
            sleep(30);
300
620k
        }
301
620k
    });
302
303
620k
    RETURN_IF_ERROR(_function->execute(context->fn_context(_fn_context_index), temp_block, args,
304
620k
                                       num_columns_without_result, count));
305
620k
    result_column = temp_block.get_by_position(num_columns_without_result).column;
306
620k
    DCHECK_EQ(result_column->size(), count);
307
620k
    RETURN_IF_ERROR(result_column->column_self_check());
308
620k
    return Status::OK();
309
620k
}
310
311
0
size_t VectorizedFnCall::estimate_memory(const size_t rows) {
312
0
    if (is_const_and_have_executed()) { // const have execute in open function
313
0
        return 0;
314
0
    }
315
316
0
    size_t estimate_size = 0;
317
0
    for (auto& child : _children) {
318
0
        estimate_size += child->estimate_memory(rows);
319
0
    }
320
321
0
    if (_data_type->have_maximum_size_of_value()) {
322
0
        estimate_size += rows * _data_type->get_size_of_value_in_memory();
323
0
    } else {
324
0
        estimate_size += rows * 512; /// FIXME: estimated value...
325
0
    }
326
0
    return estimate_size;
327
0
}
328
329
Status VectorizedFnCall::execute_runtime_filter(VExprContext* context, const Block* block,
330
                                                const uint8_t* __restrict filter, size_t count,
331
                                                ColumnPtr& result_column,
332
17.3k
                                                ColumnPtr* arg_column) const {
333
17.3k
    return _do_execute(context, block, nullptr, count, result_column, arg_column);
334
17.3k
}
335
336
Status VectorizedFnCall::execute_column_impl(VExprContext* context, const Block* block,
337
                                             const Selector* selector, size_t count,
338
628k
                                             ColumnPtr& result_column) const {
339
628k
    return _do_execute(context, block, selector, count, result_column, nullptr);
340
628k
}
341
342
327k
const std::string& VectorizedFnCall::expr_name() const {
343
327k
    return _expr_name;
344
327k
}
345
346
78
std::string VectorizedFnCall::function_name() const {
347
78
    return _function_name;
348
78
}
349
350
267
std::string VectorizedFnCall::debug_string() const {
351
267
    std::stringstream out;
352
267
    out << "VectorizedFn[";
353
267
    out << _expr_name;
354
267
    out << "]{";
355
267
    bool first = true;
356
531
    for (const auto& input_expr : children()) {
357
531
        if (first) {
358
265
            first = false;
359
266
        } else {
360
266
            out << ",";
361
266
        }
362
531
        out << "\n" << input_expr->debug_string();
363
531
    }
364
267
    out << "}";
365
267
    return out.str();
366
267
}
367
368
0
std::string VectorizedFnCall::debug_string(const std::vector<VectorizedFnCall*>& agg_fns) {
369
0
    std::stringstream out;
370
0
    out << "[";
371
0
    for (int i = 0; i < agg_fns.size(); ++i) {
372
0
        out << (i == 0 ? "" : " ") << agg_fns[i]->debug_string();
373
0
    }
374
0
    out << "]";
375
0
    return out.str();
376
0
}
377
378
2.51k
bool VectorizedFnCall::can_push_down_to_index() const {
379
2.51k
    return _function->can_push_down_to_index();
380
2.51k
}
381
382
66.7k
bool VectorizedFnCall::is_deterministic() const {
383
66.7k
    static const std::set<std::string> NON_DETERMINISTIC_FUNCTIONS = {
384
66.7k
            "random", "rand", "random_bytes", "uuid", "uuid_numeric"};
385
66.8k
    return !NON_DETERMINISTIC_FUNCTIONS.contains(_function_name) && VExpr::is_deterministic();
386
66.7k
}
387
388
51.5k
bool VectorizedFnCall::is_safe_to_execute_on_selected_rows() const {
389
51.5k
    static const std::set<std::string> ERROR_PRESERVING_FUNCTIONS = {"assert_true"};
390
51.5k
    return !ERROR_PRESERVING_FUNCTIONS.contains(_function_name) &&
391
51.5k
           VExpr::is_safe_to_execute_on_selected_rows();
392
51.5k
}
393
394
0
bool VectorizedFnCall::equals(const VExpr& other) {
395
0
    const auto* other_ptr = dynamic_cast<const VectorizedFnCall*>(&other);
396
0
    if (!other_ptr) {
397
0
        return false;
398
0
    }
399
0
    if (this->_function_name != other_ptr->_function_name) {
400
0
        return false;
401
0
    }
402
0
    if (get_num_children() != other_ptr->get_num_children()) {
403
0
        return false;
404
0
    }
405
0
    for (uint16_t i = 0; i < get_num_children(); i++) {
406
0
        if (!this->get_child(i)->equals(*other_ptr->get_child(i))) {
407
0
            return false;
408
0
        }
409
0
    }
410
0
    return true;
411
0
}
412
413
/*
414
 * For ANN range search we expect a comparison expression (LE/LT/GE/GT) whose left side is either:
415
 *   1) a vector distance function call, or
416
 *   2) a cast/virtual slot that unwraps to the function call when the planner promotes float to
417
 *      double literals.
418
 *
419
 * Visually the logical tree looks like:
420
 *
421
 *   FunctionCall(LE/LT/GE/GT)
422
 *   |----------------
423
 *   |               |
424
 *   |               |
425
 *   VirtualSlotRef* Float32Literal/Float64Literal
426
 *   |
427
 *   |
428
 *   Cast(Float -> Double)*
429
 *   |
430
 *   FunctionCall(distance)
431
 *   |----------------
432
 *   |               |
433
 *   |               |
434
 *   SlotRef         ArrayLiteral/Cast(String as Array<FLOAT>)
435
 *
436
 * Items marked with * are optional and depend on literal types/virtual column usage. The helper
437
 * below normalizes the shape and validates distance function, slot, and constant vector inputs.
438
 */
439
440
void VectorizedFnCall::prepare_ann_range_search(
441
        const doris::VectorSearchUserParams& user_params,
442
13.9k
        segment_v2::AnnRangeSearchRuntime& range_search_runtime, bool& suitable_for_ann_index) {
443
13.9k
    if (!suitable_for_ann_index) {
444
0
        return;
445
0
    }
446
447
13.9k
    if (OPS_FOR_ANN_RANGE_SEARCH.find(this->op()) == OPS_FOR_ANN_RANGE_SEARCH.end()) {
448
10.7k
        suitable_for_ann_index = false;
449
10.7k
        return;
450
10.7k
    }
451
452
3.23k
    auto mark_unsuitable = [&](const std::string& reason) {
453
3.18k
        suitable_for_ann_index = false;
454
18.4E
        VLOG_DEBUG << "ANN range search skipped: " << reason;
455
3.18k
    };
456
457
3.23k
    range_search_runtime.is_le_or_lt =
458
3.23k
            (this->op() == TExprOpcode::LE || this->op() == TExprOpcode::LT);
459
460
3.23k
    DCHECK(_children.size() == 2);
461
462
3.23k
    auto left_child = get_child(0);
463
3.23k
    auto right_child = get_child(1);
464
465
    // ========== Step 1: Check left child - must be a distance function ==========
466
3.23k
    auto get_virtual_expr = [&](const VExprSPtr& expr,
467
4.06k
                                std::shared_ptr<VirtualSlotRef>& slot_ref) -> VExprSPtr {
468
4.06k
        auto virtual_ref = std::dynamic_pointer_cast<VirtualSlotRef>(expr);
469
4.06k
        if (virtual_ref != nullptr) {
470
119
            DCHECK(virtual_ref->get_virtual_column_expr() != nullptr);
471
119
            slot_ref = virtual_ref;
472
119
            return virtual_ref->get_virtual_column_expr();
473
119
        }
474
3.94k
        return expr;
475
4.06k
    };
476
477
3.23k
    std::shared_ptr<VirtualSlotRef> vir_slot_ref;
478
3.23k
    auto normalized_left = get_virtual_expr(left_child, vir_slot_ref);
479
480
    // Try to find the distance function call, it may be wrapped in a Cast(Float->Double)
481
3.23k
    std::shared_ptr<VectorizedFnCall> function_call =
482
3.23k
            std::dynamic_pointer_cast<VectorizedFnCall>(normalized_left);
483
3.23k
    bool has_float_to_double_cast = false;
484
485
3.23k
    if (function_call == nullptr) {
486
        // Check if it's a Cast expression wrapping a function call
487
1.09k
        auto cast_expr = std::dynamic_pointer_cast<VCastExpr>(normalized_left);
488
1.09k
        if (cast_expr == nullptr) {
489
244
            mark_unsuitable("Left child is neither a function call nor a cast expression.");
490
244
            return;
491
244
        }
492
847
        has_float_to_double_cast = true;
493
847
        auto normalized_cast_child = get_virtual_expr(cast_expr->get_child(0), vir_slot_ref);
494
847
        function_call = std::dynamic_pointer_cast<VectorizedFnCall>(normalized_cast_child);
495
847
        if (function_call == nullptr) {
496
805
            mark_unsuitable("Left child of cast is not a function call.");
497
805
            return;
498
805
        }
499
847
    }
500
501
    // Check if it's a supported distance function
502
2.19k
    if (DISTANCE_FUNCS.find(function_call->_function_name) == DISTANCE_FUNCS.end()) {
503
2.13k
        mark_unsuitable(fmt::format("Left child is not a supported distance function: {}",
504
2.13k
                                    function_call->_function_name));
505
2.13k
        return;
506
2.13k
    }
507
508
    // Strip the _approximate suffix to get metric type
509
56
    std::string metric_name = function_call->_function_name;
510
56
    metric_name = metric_name.substr(0, metric_name.size() - 12);
511
56
    range_search_runtime.metric_type = segment_v2::string_to_metric(metric_name);
512
513
    // ========== Step 2: Validate distance function arguments ==========
514
    // Identify the slot ref child and the constant query array child (ArrayLiteral or CAST to array)
515
56
    Int32 idx_of_slot_ref = -1;
516
56
    Int32 idx_of_array_expr = -1;
517
93
    auto classify_child = [&](const VExprSPtr& child, UInt16 index) {
518
93
        if (idx_of_slot_ref == -1 && std::dynamic_pointer_cast<VSlotRef>(child) != nullptr) {
519
47
            idx_of_slot_ref = index;
520
47
            return;
521
47
        }
522
46
        if (idx_of_array_expr == -1 &&
523
47
            (std::dynamic_pointer_cast<VArrayLiteral>(child) != nullptr ||
524
47
             std::dynamic_pointer_cast<VCastExpr>(child) != nullptr)) {
525
40
            idx_of_array_expr = index;
526
40
        }
527
46
    };
528
529
149
    for (UInt16 i = 0; i < function_call->get_num_children(); ++i) {
530
93
        classify_child(function_call->get_child(i), i);
531
93
    }
532
533
56
    if (idx_of_slot_ref == -1 || idx_of_array_expr == -1) {
534
6
        mark_unsuitable("slot ref or array literal/cast is missing.");
535
6
        return;
536
6
    }
537
538
50
    auto slot_ref = std::dynamic_pointer_cast<VSlotRef>(
539
50
            function_call->get_child(static_cast<UInt16>(idx_of_slot_ref)));
540
50
    range_search_runtime.src_col_idx = slot_ref->column_id();
541
50
    range_search_runtime.dst_col_idx = vir_slot_ref == nullptr ? -1 : vir_slot_ref->column_id();
542
543
    // Materialize the constant array expression and validate its shape and types
544
50
    auto array_expr = function_call->get_child(static_cast<UInt16>(idx_of_array_expr));
545
50
    auto extract_result = extract_query_vector(array_expr);
546
50
    if (!extract_result.has_value()) {
547
0
        mark_unsuitable("Failed to extract query vector from constant array expression.");
548
0
        return;
549
0
    }
550
50
    range_search_runtime.query_value = extract_result.value();
551
50
    range_search_runtime.dim = range_search_runtime.query_value->size();
552
553
    // ========== Step 3: Check right child - must be a float/double literal ==========
554
50
    auto right_literal = std::dynamic_pointer_cast<VLiteral>(right_child);
555
50
    if (right_literal == nullptr) {
556
1
        mark_unsuitable("Right child is not a literal.");
557
1
        return;
558
1
    }
559
560
    // Handle nullable literal gracefully - just mark as unsuitable instead of crash
561
49
    if (right_literal->is_nullable()) {
562
0
        mark_unsuitable("Right literal is nullable, not supported for ANN range search.");
563
0
        return;
564
0
    }
565
566
49
    auto right_type = right_literal->get_data_type();
567
49
    PrimitiveType right_primitive = right_type->get_primitive_type();
568
49
    const bool float32_literal = right_primitive == PrimitiveType::TYPE_FLOAT;
569
49
    const bool float64_literal = right_primitive == PrimitiveType::TYPE_DOUBLE;
570
571
49
    if (!float32_literal && !float64_literal) {
572
0
        mark_unsuitable("Right child is not a Float32Literal or Float64Literal.");
573
0
        return;
574
0
    }
575
576
    // Validate consistency: if we have Cast(Float->Double), right must be double literal
577
49
    if (has_float_to_double_cast && !float64_literal) {
578
0
        mark_unsuitable("Cast expression expects double literal on right side.");
579
0
        return;
580
0
    }
581
582
    // Extract radius value
583
49
    auto right_col = right_literal->get_column_ptr()->convert_to_full_column_if_const();
584
49
    if (float32_literal) {
585
7
        const ColumnFloat32* cf32_right = assert_cast<const ColumnFloat32*>(right_col.get());
586
7
        range_search_runtime.radius = cf32_right->get_data()[0];
587
42
    } else {
588
42
        const ColumnFloat64* cf64_right = assert_cast<const ColumnFloat64*>(right_col.get());
589
42
        range_search_runtime.radius = static_cast<float>(cf64_right->get_data()[0]);
590
42
    }
591
592
    // ========== Done: Mark as suitable for ANN range search ==========
593
49
    range_search_runtime.is_ann_range_search = true;
594
49
    range_search_runtime.user_params = user_params;
595
49
    VLOG_DEBUG << fmt::format("Ann range search params: {}", range_search_runtime.to_string());
596
49
    return;
597
49
}
598
599
Status VectorizedFnCall::evaluate_ann_range_search(
600
        const segment_v2::AnnRangeSearchRuntime& range_search_runtime,
601
        const std::vector<std::unique_ptr<segment_v2::IndexIterator>>& cid_to_index_iterators,
602
        const std::vector<ColumnId>& idx_to_cid,
603
        const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>& column_iterators,
604
        size_t rows_of_segment, roaring::Roaring& row_bitmap,
605
        segment_v2::AnnIndexStats& ann_index_stats, bool enable_result_cache,
606
11.0k
        AnnRangeSearchEvaluationResult& evaluation_result) {
607
11.0k
    evaluation_result = {};
608
11.0k
    if (range_search_runtime.is_ann_range_search == false) {
609
11.0k
        return Status::OK();
610
11.0k
    }
611
612
41
    VLOG_DEBUG << fmt::format("Try apply ann range search. Local search params: {}",
613
3
                              range_search_runtime.to_string());
614
41
    size_t origin_num = row_bitmap.cardinality();
615
616
41
    const auto idx_in_block = range_search_runtime.src_col_idx;
617
41
    DCHECK_LT(idx_in_block, idx_to_cid.size())
618
0
            << "idx_in_block: " << idx_in_block << ", idx_to_cid.size(): " << idx_to_cid.size();
619
620
41
    ColumnId src_col_cid = idx_to_cid[idx_in_block];
621
41
    DCHECK(src_col_cid < cid_to_index_iterators.size());
622
41
    segment_v2::IndexIterator* index_iterator = cid_to_index_iterators[src_col_cid].get();
623
41
    if (index_iterator == nullptr) {
624
1
        VLOG_DEBUG << "ANN range search skipped: "
625
0
                   << fmt::format("No index iterator for column cid {}", src_col_cid);
626
1
        ;
627
1
        return Status::OK();
628
1
    }
629
630
40
    segment_v2::AnnIndexIterator* ann_index_iterator =
631
40
            dynamic_cast<segment_v2::AnnIndexIterator*>(index_iterator);
632
40
    if (ann_index_iterator == nullptr) {
633
0
        VLOG_DEBUG << "ANN range search skipped: "
634
0
                   << fmt::format("Column cid {} has no ANN index iterator", src_col_cid);
635
0
        return Status::OK();
636
0
    }
637
40
    DCHECK(ann_index_iterator->get_reader(AnnIndexReaderType::ANN) != nullptr)
638
3
            << "Ann index iterator should have reader. Column cid: " << src_col_cid;
639
40
    std::shared_ptr<AnnIndexReader> ann_index_reader = std::dynamic_pointer_cast<AnnIndexReader>(
640
40
            ann_index_iterator->get_reader(segment_v2::AnnIndexReaderType::ANN));
641
40
    DCHECK(ann_index_reader != nullptr)
642
3
            << "Ann index reader should not be null. Column cid: " << src_col_cid;
643
    // Check if metrics type is match.
644
40
    if (ann_index_reader->get_metric_type() != range_search_runtime.metric_type) {
645
0
        VLOG_DEBUG << "ANN range search skipped: "
646
0
                   << fmt::format("Metric type mismatch. Index={} Query={}",
647
0
                                  segment_v2::metric_to_string(ann_index_reader->get_metric_type()),
648
0
                                  segment_v2::metric_to_string(range_search_runtime.metric_type));
649
0
        return Status::OK();
650
0
    }
651
652
    // Check dimension if available (>0)
653
40
    const size_t index_dim = ann_index_reader->get_dimension();
654
40
    if (index_dim > 0 && index_dim != range_search_runtime.dim) {
655
8
        return Status::InvalidArgument(
656
8
                "Ann range search query dimension {} does not match index dimension {}",
657
8
                range_search_runtime.dim, index_dim);
658
8
    }
659
660
32
    const auto& user_params = range_search_runtime.user_params;
661
32
    if (user_params.should_fallback_ann_index_by_small_candidate(origin_num, rows_of_segment)) {
662
0
        VLOG_DEBUG << fmt::format(
663
0
                "Ann range search input rows {} reach small candidate threshold, "
664
0
                "rows_of_segment: {}, absolute_threshold: {}, percent_threshold: {}, "
665
0
                "will not use ann index to filter",
666
0
                origin_num, rows_of_segment, user_params.ann_index_candidate_rows_threshold,
667
0
                user_params.ann_index_candidate_rows_percent_threshold);
668
0
        ann_index_stats.fall_back_brute_force_cnt += 1;
669
0
        ann_index_stats.range_fallback_by_small_candidate_cnt += 1;
670
0
        ann_index_stats.range_fallback_small_candidate_rows += origin_num;
671
0
        return Status::OK();
672
0
    }
673
674
32
    auto stats = std::make_unique<segment_v2::AnnIndexStats>();
675
    // Track load index timing
676
32
    {
677
32
        SCOPED_TIMER(&(stats->load_index_costs_ns));
678
32
        if (!ann_index_iterator->try_load_index()) {
679
2
            VLOG_DEBUG << "ANN range search skipped: "
680
0
                       << fmt::format("Failed to load ANN index for column cid {}", src_col_cid);
681
2
            ann_index_stats.fall_back_brute_force_cnt += 1;
682
2
            return Status::OK();
683
2
        }
684
30
        double load_costs_ms = static_cast<double>(stats->load_index_costs_ns.value()) / 1000000.0;
685
30
        DorisMetrics::instance()->ann_index_load_costs_ms->increment(
686
30
                static_cast<int64_t>(load_costs_ms));
687
30
    }
688
689
0
    AnnRangeSearchParams params = range_search_runtime.to_range_search_params();
690
691
30
    params.roaring = &row_bitmap;
692
30
    params.enable_result_cache = enable_result_cache;
693
30
    DCHECK(params.roaring != nullptr);
694
30
    DCHECK(params.query_value != nullptr);
695
30
    segment_v2::AnnRangeSearchResult result;
696
30
    RETURN_IF_ERROR(ann_index_iterator->range_search(params, range_search_runtime.user_params,
697
30
                                                     &result, stats.get()));
698
699
30
#ifndef NDEBUG
700
30
    if (range_search_runtime.is_le_or_lt == false &&
701
30
        ann_index_reader->get_metric_type() == AnnIndexMetric::L2) {
702
7
        DCHECK(result.distance == nullptr) << "Should not have distance";
703
7
    }
704
30
    if (range_search_runtime.is_le_or_lt == true &&
705
30
        ann_index_reader->get_metric_type() == AnnIndexMetric::IP) {
706
4
        DCHECK(result.distance == nullptr);
707
4
    }
708
30
#endif
709
30
    DCHECK(result.roaring != nullptr);
710
30
    row_bitmap = *result.roaring;
711
712
    // Process virtual column
713
30
    bool dist_fulfilled = false;
714
30
    if (range_search_runtime.dst_col_idx >= 0) {
715
        // Prepare materialization if we can use result from index.
716
        // Typical situation: range search and operator is LE or LT.
717
4
        if (result.distance != nullptr) {
718
2
            DCHECK(result.row_ids != nullptr);
719
2
            ColumnId dst_col_cid = idx_to_cid[range_search_runtime.dst_col_idx];
720
2
            DCHECK(dst_col_cid < column_iterators.size());
721
2
            DCHECK(column_iterators[dst_col_cid] != nullptr);
722
2
            segment_v2::ColumnIterator* column_iterator = column_iterators[dst_col_cid].get();
723
2
            DCHECK(column_iterator != nullptr);
724
2
            segment_v2::VirtualColumnIterator* virtual_column_iterator =
725
2
                    dynamic_cast<segment_v2::VirtualColumnIterator*>(column_iterator);
726
2
            DCHECK(virtual_column_iterator != nullptr);
727
            // Now convert distance to column
728
2
            size_t size = result.roaring->cardinality();
729
2
            auto distance_col = ColumnFloat32::create(size);
730
2
            const float* src = result.distance.get();
731
2
            float* dst = distance_col->get_data().data();
732
15
            for (size_t i = 0; i < size; ++i) {
733
13
                dst[i] = src[i];
734
13
            }
735
2
            virtual_column_iterator->prepare_materialization(std::move(distance_col),
736
2
                                                             std::move(result.row_ids));
737
2
            dist_fulfilled = true;
738
2
        } else {
739
            // Whether the ANN index should have produced distance depends on metric and operator:
740
            //  - L2: distance is produced for LE/LT; not produced for GE/GT
741
            //  - IP: distance is produced for GE/GT; not produced for LE/LT
742
2
#ifndef NDEBUG
743
2
            const bool should_have_distance =
744
2
                    (range_search_runtime.is_le_or_lt &&
745
2
                     range_search_runtime.metric_type == AnnIndexMetric::L2) ||
746
2
                    (!range_search_runtime.is_le_or_lt &&
747
2
                     range_search_runtime.metric_type == AnnIndexMetric::IP);
748
            // If we expected distance but didn't get it, assert in debug to catch logic errors.
749
2
            DCHECK(!should_have_distance) << "Expected distance from ANN index but got none";
750
2
#endif
751
2
        }
752
26
    } else {
753
        // Dest is not virtual column.
754
26
        dist_fulfilled = true;
755
26
    }
756
757
30
    evaluation_result.executed = true;
758
30
    evaluation_result.dist_fulfilled = dist_fulfilled;
759
30
    VLOG_DEBUG << fmt::format(
760
3
            "Ann range search filtered {} rows, origin {} rows, virtual column is full-filled: {}",
761
3
            origin_num - row_bitmap.cardinality(), origin_num, dist_fulfilled);
762
763
30
    ann_index_stats = *stats;
764
30
    return Status::OK();
765
30
}
766
767
939k
double VectorizedFnCall::execute_cost() const {
768
939k
    if (!_function) {
769
0
        throw Exception(
770
0
                Status::InternalError("Function is null in expression: {}", this->debug_string()));
771
0
    }
772
939k
    double cost = _function->execute_cost();
773
1.87M
    for (const auto& child : _children) {
774
1.87M
        cost += child->execute_cost();
775
1.87M
    }
776
939k
    return cost;
777
939k
}
778
779
} // namespace doris