Coverage Report

Created: 2026-04-10 18:35

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