Coverage Report

Created: 2026-04-14 10:07

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