Coverage Report

Created: 2026-03-20 04:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/vec/exprs/vsearch.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 "vec/exprs/vsearch.h"
19
20
#include <memory>
21
#include <roaring/roaring.hh>
22
23
#include "common/logging.h"
24
#include "common/status.h"
25
#include "glog/logging.h"
26
#include "olap/rowset/segment_v2/inverted_index_reader.h"
27
#include "runtime/runtime_state.h"
28
#include "vec/columns/column_const.h"
29
#include "vec/exprs/vexpr_context.h"
30
#include "vec/exprs/vliteral.h"
31
#include "vec/exprs/vslot_ref.h"
32
#include "vec/functions/function_search.h"
33
34
namespace doris::vectorized {
35
using namespace segment_v2;
36
37
namespace {
38
39
struct SearchInputBundle {
40
    std::unordered_map<std::string, IndexIterator*> iterators;
41
    std::unordered_map<std::string, vectorized::IndexFieldNameAndTypePair> field_types;
42
    std::vector<int> column_ids;
43
    vectorized::ColumnsWithTypeAndName literal_args;
44
    std::unordered_map<std::string, int> field_name_to_column_id;
45
};
46
47
Status collect_search_inputs(const VSearchExpr& expr, VExprContext* context,
48
4
                             SearchInputBundle* bundle) {
49
4
    DCHECK(bundle != nullptr);
50
51
4
    auto index_context = context->get_index_context();
52
4
    if (index_context == nullptr) {
53
0
        LOG(WARNING) << "collect_search_inputs: No inverted index context available";
54
0
        return Status::InternalError("No inverted index context available");
55
0
    }
56
57
    // Get field bindings for variant subcolumn support
58
4
    const auto& search_param = expr.get_search_param();
59
4
    const auto& field_bindings = search_param.field_bindings;
60
61
4
    int child_index = 0; // Index for iterating through children
62
4
    for (const auto& child : expr.children()) {
63
4
        if (child->is_slot_ref()) {
64
3
            auto* column_slot_ref = assert_cast<VSlotRef*>(child.get());
65
3
            int column_id = column_slot_ref->column_id();
66
3
            auto* iterator = index_context->get_inverted_index_iterator_by_column_id(column_id);
67
68
            // Determine the field_name from field_bindings (for variant subcolumns)
69
            // field_bindings and children should have the same order
70
3
            std::string field_name;
71
3
            if (child_index < field_bindings.size()) {
72
                // Use field_name from binding (may include "parent.subcolumn" for variant)
73
3
                field_name = field_bindings[child_index].field_name;
74
3
            } else {
75
                // Fallback to column_name if binding not found
76
0
                field_name = column_slot_ref->column_name();
77
0
            }
78
79
            // Only collect fields that have iterators (materialized columns with indexes)
80
3
            if (iterator != nullptr) {
81
2
                const auto* storage_name_type =
82
2
                        index_context->get_storage_name_and_type_by_column_id(column_id);
83
2
                if (storage_name_type == nullptr) {
84
1
                    return Status::InternalError("storage_name_type not found for column {} in {}",
85
1
                                                 column_id, expr.expr_name());
86
1
                }
87
88
1
                bundle->iterators.emplace(field_name, iterator);
89
1
                bundle->field_types.emplace(field_name, *storage_name_type);
90
1
                bundle->column_ids.emplace_back(column_id);
91
1
            }
92
93
2
            child_index++;
94
2
        } else if (child->is_literal()) {
95
0
            auto* literal = assert_cast<VLiteral*>(child.get());
96
0
            bundle->literal_args.emplace_back(literal->get_column_ptr(), literal->get_data_type(),
97
0
                                              literal->expr_name());
98
1
        } else {
99
            // Check if this is ElementAt expression (for variant subcolumn access)
100
1
            if (child->expr_name() == "element_at" && child_index < field_bindings.size() &&
101
1
                field_bindings[child_index].__isset.is_variant_subcolumn &&
102
1
                field_bindings[child_index].is_variant_subcolumn) {
103
                // Variant subcolumn not materialized - skip, will create empty BitSetQuery in function_search
104
0
                child_index++;
105
0
                continue;
106
0
            }
107
108
            // Not a supported child type
109
1
            return Status::InvalidArgument("Unsupported child node type: {}", child->expr_name());
110
1
        }
111
4
    }
112
113
2
    return Status::OK();
114
4
}
115
116
} // namespace
117
118
77
VSearchExpr::VSearchExpr(const TExprNode& node) : VExpr(node) {
119
77
    if (node.__isset.search_param) {
120
73
        _search_param = node.search_param;
121
73
        _original_dsl = _search_param.original_dsl;
122
73
    }
123
77
}
124
125
Status VSearchExpr::prepare(RuntimeState* state, const RowDescriptor& row_desc,
126
0
                            VExprContext* context) {
127
0
    RETURN_IF_ERROR(VExpr::prepare(state, row_desc, context));
128
0
    const auto& query_options = state->query_options();
129
0
    if (query_options.__isset.enable_inverted_index_query_cache) {
130
0
        _enable_cache = query_options.enable_inverted_index_query_cache;
131
0
    }
132
0
    return Status::OK();
133
0
}
134
135
4
const std::string& VSearchExpr::expr_name() const {
136
4
    static const std::string name = "VSearchExpr";
137
4
    return name;
138
4
}
139
140
Status VSearchExpr::execute_column(VExprContext* context, const Block* block, Selector* selector,
141
3
                                   size_t count, ColumnPtr& result_column) const {
142
3
    if (fast_execute(context, selector, count, result_column)) {
143
1
        return Status::OK();
144
1
    }
145
146
2
    return Status::InternalError("SearchExpr should not be executed without inverted index");
147
3
}
148
149
22
Status VSearchExpr::evaluate_inverted_index(VExprContext* context, uint32_t segment_num_rows) {
150
22
    LOG(INFO) << "VSearchExpr::evaluate_inverted_index called, DSL: " << _search_param.original_dsl;
151
152
22
    if (_search_param.original_dsl.empty()) {
153
3
        return Status::InvalidArgument("search DSL is empty");
154
3
    }
155
156
19
    auto index_context = context->get_index_context();
157
19
    if (!index_context) {
158
15
        LOG(WARNING) << "VSearchExpr: No inverted index context available";
159
15
        return Status::OK();
160
15
    }
161
162
4
    SearchInputBundle bundle;
163
4
    RETURN_IF_ERROR(collect_search_inputs(*this, context, &bundle));
164
165
2
    VLOG_DEBUG << "VSearchExpr: bundle.iterators.size()=" << bundle.iterators.size();
166
167
2
    if (bundle.iterators.empty()) {
168
1
        LOG(WARNING) << "VSearchExpr: No indexed columns available for evaluation, DSL: "
169
1
                     << _original_dsl;
170
1
        auto empty_bitmap = InvertedIndexResultBitmap(std::make_shared<roaring::Roaring>(),
171
1
                                                      std::make_shared<roaring::Roaring>());
172
1
        index_context->set_index_result_for_expr(this, std::move(empty_bitmap));
173
1
        return Status::OK();
174
1
    }
175
176
1
    auto index_query_context = index_context->get_index_query_context();
177
178
1
    auto function = std::make_shared<FunctionSearch>();
179
1
    auto result_bitmap = InvertedIndexResultBitmap();
180
1
    auto status = function->evaluate_inverted_index_with_search_param(
181
1
            _search_param, bundle.field_types, bundle.iterators, segment_num_rows, result_bitmap,
182
1
            _enable_cache, index_context.get(), bundle.field_name_to_column_id,
183
1
            index_query_context);
184
185
1
    if (!status.ok()) {
186
1
        LOG(WARNING) << "VSearchExpr: Function evaluation failed: " << status.to_string();
187
1
        return status;
188
1
    }
189
190
0
    index_context->set_index_result_for_expr(this, result_bitmap);
191
0
    for (int column_id : bundle.column_ids) {
192
0
        index_context->set_true_for_index_status(this, column_id);
193
0
    }
194
195
0
    return Status::OK();
196
1
}
197
198
} // namespace doris::vectorized