Coverage Report

Created: 2026-03-12 16:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exprs/virtual_slot_ref.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/virtual_slot_ref.h"
19
20
#include <gen_cpp/Exprs_types.h>
21
#include <glog/logging.h>
22
#include <thrift/protocol/TDebugProtocol.h>
23
24
#include <ostream>
25
#include <vector>
26
27
#include "common/exception.h"
28
#include "common/logging.h"
29
#include "common/status.h"
30
#include "core/block/block.h"
31
#include "core/block/column_with_type_and_name.h"
32
#include "core/column/column.h"
33
#include "core/column/column_nothing.h"
34
#include "exprs/vectorized_fn_call.h"
35
#include "exprs/vexpr_context.h"
36
#include "exprs/vexpr_fwd.h"
37
#include "runtime/descriptors.h"
38
#include "runtime/runtime_state.h"
39
namespace doris {
40
#include "common/compile_check_begin.h"
41
VirtualSlotRef::VirtualSlotRef(const doris::TExprNode& node)
42
279
        : VExpr(node),
43
279
          _column_id(-1),
44
279
          _slot_id(node.slot_ref.slot_id),
45
279
          _column_name(nullptr),
46
279
          _column_label(node.label) {}
47
48
VirtualSlotRef::VirtualSlotRef(const SlotDescriptor* desc)
49
6
        : VExpr(desc->type(), false), _column_id(-1), _slot_id(desc->id()), _column_name(nullptr) {}
50
51
Status VirtualSlotRef::prepare(doris::RuntimeState* state, const doris::RowDescriptor& desc,
52
246
                               VExprContext* context) {
53
246
    RETURN_IF_ERROR_OR_PREPARED(VExpr::prepare(state, desc, context));
54
246
    DCHECK_EQ(_children.size(), 0);
55
246
    if (_slot_id == -1) {
56
3
        _prepare_finished = true;
57
3
        return Status::OK();
58
3
    }
59
60
243
    const SlotDescriptor* slot_desc = state->desc_tbl().get_slot_descriptor(_slot_id);
61
243
    if (slot_desc == nullptr) {
62
0
        return Status::Error<ErrorCode::INTERNAL_ERROR>(
63
0
                "couldn't resolve slot descriptor {}, desc: {}", _slot_id,
64
0
                state->desc_tbl().debug_string());
65
0
    }
66
67
243
    if (slot_desc->get_virtual_column_expr() == nullptr) {
68
0
        return Status::InternalError(
69
0
                "VirtualSlotRef {} has no virtual column expr, slot_id: {}, desc: {}, "
70
0
                "slot_desc: {}, desc_tbl: {}",
71
0
                *_column_name, _slot_id, desc.debug_string(), slot_desc->debug_string(),
72
0
                state->desc_tbl().debug_string());
73
0
    }
74
75
243
    _column_name = &slot_desc->col_name();
76
243
    _column_data_type = slot_desc->get_data_type_ptr();
77
243
    DCHECK(_column_data_type != nullptr);
78
243
    _column_id = desc.get_column_id(_slot_id);
79
243
    if (_column_id < 0) {
80
0
        return Status::Error<ErrorCode::INTERNAL_ERROR>(
81
0
                "VirtualSlotRef {} has invalid slot id: "
82
0
                "{}.\nslot_desc:\n{},\ndesc:\n{},\ndesc_tbl:\n{}",
83
0
                *_column_name, _slot_id, slot_desc->debug_string(), desc.debug_string(),
84
0
                state->desc_tbl().debug_string());
85
0
    }
86
243
    const TExpr& expr = *slot_desc->get_virtual_column_expr();
87
    // Create a temp_ctx only for create_expr_tree.
88
243
    VExprContextSPtr temp_ctx;
89
243
    RETURN_IF_ERROR(VExpr::create_expr_tree(expr, temp_ctx));
90
243
    _virtual_column_expr = temp_ctx->root();
91
    // Virtual column expr should do prepare with original context.
92
243
    RETURN_IF_ERROR(_virtual_column_expr->prepare(state, desc, context));
93
243
    _prepare_finished = true;
94
243
    return Status::OK();
95
243
}
96
97
Status VirtualSlotRef::open(RuntimeState* state, VExprContext* context,
98
1.11k
                            FunctionContext::FunctionStateScope scope) {
99
1.11k
    DCHECK(_prepare_finished);
100
1.11k
    RETURN_IF_ERROR(_virtual_column_expr->open(state, context, scope));
101
1.11k
    RETURN_IF_ERROR(VExpr::open(state, context, scope));
102
1.11k
    _open_finished = true;
103
1.11k
    return Status::OK();
104
1.11k
}
105
106
Status VirtualSlotRef::execute_column(VExprContext* context, const Block* block, Selector* selector,
107
319
                                      size_t count, ColumnPtr& result_column) const {
108
320
    if (_column_id >= 0 && _column_id >= block->columns()) {
109
0
        return Status::Error<ErrorCode::INTERNAL_ERROR>(
110
0
                "input block not contain slot column {}, column_id={}, block={}", *_column_name,
111
0
                _column_id, block->dump_structure());
112
0
    }
113
114
319
    ColumnWithTypeAndName col_type_name = block->get_by_position(_column_id);
115
116
319
    if (!col_type_name.column) {
117
        // Maybe we need to create a column in this situation.
118
0
        return Status::InternalError(
119
0
                "VirtualSlotRef column is null, column_id: {}, column_name: {}", _column_id,
120
0
                *_column_name);
121
0
    }
122
123
319
    const auto* col_nothing = check_and_get_column<ColumnNothing>(col_type_name.column.get());
124
319
    bool column_from_virtual_column_expr = false;
125
320
    if (this->_virtual_column_expr != nullptr) {
126
320
        if (col_nothing != nullptr) {
127
            // Virtual column is not materialized, so we need to materialize it.
128
            // Note: After executing 'execute', we cannot use the column from line 120 in subsequent code,
129
            // because the vector might be resized during execution, causing previous references to become invalid.
130
4
            ColumnPtr tmp_column;
131
4
            RETURN_IF_ERROR(_virtual_column_expr->execute_column(context, block, selector, count,
132
4
                                                                 tmp_column));
133
4
            result_column = std::move(tmp_column);
134
4
            column_from_virtual_column_expr = true;
135
136
4
            VLOG_DEBUG << fmt::format(
137
0
                    "Materialization of virtual column, slot_id {}, column_id {}, "
138
0
                    "column_name {}, column size {}",
139
0
                    _slot_id, _column_id, *_column_name,
140
0
                    block->get_by_position(_column_id).column->size());
141
4
        }
142
143
320
#ifndef NDEBUG
144
        // get_by_position again since vector in block may be resized
145
320
        col_type_name = block->get_by_position(_column_id);
146
320
        DCHECK(col_type_name.type != nullptr);
147
320
        if (!_column_data_type->equals(*col_type_name.type)) {
148
0
            throw doris::Exception(doris::ErrorCode::FATAL_ERROR,
149
0
                                   "Virtual column type not match, column_id: {}, "
150
0
                                   "column_name: {}, column_type: {}, virtual_column_type: {}",
151
0
                                   _column_id, *_column_name, col_type_name.type->get_name(),
152
0
                                   _column_data_type->get_name());
153
0
        }
154
320
#endif
155
18.4E
    } else {
156
        // This is a virtual slot ref that not pushed to segment_iterator
157
18.4E
        if (col_nothing == nullptr) {
158
0
            return Status::InternalError("Logical error, virtual column can not be materialized");
159
18.4E
        } else {
160
18.4E
            return Status::OK();
161
18.4E
        }
162
18.4E
    }
163
164
320
    if (!column_from_virtual_column_expr) {
165
        // if the column is from virtual column expr, result_column has been filter already
166
316
        result_column = filter_column_with_selector(col_type_name.column, selector, count);
167
316
    }
168
169
320
    DCHECK_EQ(result_column->size(), count);
170
320
    return Status::OK();
171
319
}
172
173
303
const std::string& VirtualSlotRef::expr_name() const {
174
303
    return *_column_name;
175
303
}
176
2
std::string VirtualSlotRef::expr_label() {
177
2
    return _column_label;
178
2
}
179
180
2
std::string VirtualSlotRef::debug_string() const {
181
2
    std::stringstream out;
182
2
    out << "VirtualSlotRef(slot_id=" << _slot_id << VExpr::debug_string() << ")";
183
2
    return out.str();
184
2
}
185
186
25
bool VirtualSlotRef::equals(const VExpr& other) {
187
25
    const auto* other_ptr = dynamic_cast<const VirtualSlotRef*>(&other);
188
25
    if (!other_ptr) {
189
3
        return false;
190
3
    }
191
192
    // Compare slot_id and column_id
193
22
    if (this->_slot_id != other_ptr->_slot_id || this->_column_id != other_ptr->_column_id) {
194
7
        return false;
195
7
    }
196
197
    // Compare column_name pointers properly
198
15
    if (this->_column_name == nullptr && other_ptr->_column_name == nullptr) {
199
        // Both are null, they are equal
200
8
    } else if (this->_column_name == nullptr || other_ptr->_column_name == nullptr) {
201
        // One is null, the other is not, they are not equal
202
2
        return false;
203
5
    } else if (*this->_column_name != *other_ptr->_column_name) {
204
        // Both are not null, compare the string contents
205
3
        return false;
206
3
    }
207
208
    // Compare column_label
209
10
    if (this->_column_label != other_ptr->_column_label) {
210
3
        return false;
211
3
    }
212
213
7
    return true;
214
10
}
215
216
/**
217
 * @brief Implements ANN range search evaluation for virtual slot references.
218
 * 
219
 * This method handles the case where a virtual slot reference wraps a distance
220
 * function call that can be optimized using ANN index range search. Instead of
221
 * computing distances for all rows, it delegates to the underlying virtual
222
 * expression to perform the optimized search.
223
 * 
224
 * @param range_search_runtime Runtime parameters for the range search
225
 * @param cid_to_index_iterators Index iterators for each column
226
 * @param idx_to_cid Column ID mapping
227
 * @param column_iterators Data column iterators
228
 * @param row_bitmap Result bitmap to be updated with matching rows
229
 * @param ann_index_stats Performance statistics collector
230
 * @return Status::OK() if successful, error status otherwise
231
 */
232
Status VirtualSlotRef::evaluate_ann_range_search(
233
        const segment_v2::AnnRangeSearchRuntime& range_search_runtime,
234
        const std::vector<std::unique_ptr<segment_v2::IndexIterator>>& cid_to_index_iterators,
235
        const std::vector<ColumnId>& idx_to_cid,
236
        const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>& column_iterators,
237
0
        roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats& ann_index_stats) {
238
0
    return _virtual_column_expr->evaluate_ann_range_search(
239
0
            range_search_runtime, cid_to_index_iterators, idx_to_cid, column_iterators, row_bitmap,
240
0
            ann_index_stats);
241
242
0
    return Status::OK();
243
0
}
244
#include "common/compile_check_end.h"
245
} // namespace doris