Coverage Report

Created: 2026-07-14 15:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/point_query_executor.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 "service/point_query_executor.h"
19
20
#include <fmt/format.h>
21
#include <gen_cpp/Descriptors_types.h>
22
#include <gen_cpp/Exprs_types.h>
23
#include <gen_cpp/PaloInternalService_types.h>
24
#include <gen_cpp/internal_service.pb.h>
25
#include <glog/logging.h>
26
#include <google/protobuf/extension_set.h>
27
#include <stdlib.h>
28
29
#include <memory>
30
#include <unordered_map>
31
#include <vector>
32
33
#include "cloud/cloud_tablet.h"
34
#include "cloud/config.h"
35
#include "common/cast_set.h"
36
#include "common/consts.h"
37
#include "common/status.h"
38
#include "core/data_type/data_type_factory.hpp"
39
#include "core/data_type_serde/data_type_serde.h"
40
#include "exec/sink/writer/vmysql_result_writer.h"
41
#include "exprs/vexpr.h"
42
#include "exprs/vexpr_context.h"
43
#include "exprs/vexpr_fwd.h"
44
#include "exprs/vslot_ref.h"
45
#include "runtime/descriptors.h"
46
#include "runtime/exec_env.h"
47
#include "runtime/result_block_buffer.h"
48
#include "runtime/runtime_profile.h"
49
#include "runtime/runtime_state.h"
50
#include "runtime/thread_context.h"
51
#include "storage/row_cursor.h"
52
#include "storage/rowset/beta_rowset.h"
53
#include "storage/rowset/rowset_fwd.h"
54
#include "storage/segment/column_reader.h"
55
#include "storage/tablet/tablet_schema.h"
56
#include "storage/utils.h"
57
#include "util/defer_op.h"
58
#include "util/jsonb/serialize.h"
59
#include "util/lru_cache.h"
60
#include "util/simd/bits.h"
61
#include "util/thrift_util.h"
62
63
namespace doris {
64
65
class PointQueryResultBlockBuffer final : public MySQLResultBlockBuffer {
66
public:
67
212
    PointQueryResultBlockBuffer(RuntimeState* state) : MySQLResultBlockBuffer(state) {}
68
    ~PointQueryResultBlockBuffer() override = default;
69
213
    std::shared_ptr<TFetchDataResult> get_block() {
70
213
        std::lock_guard<std::mutex> l(_lock);
71
213
        DCHECK_EQ(_result_batch_queue.size(), 1);
72
213
        auto result = std::move(_result_batch_queue.front());
73
213
        _result_batch_queue.pop_front();
74
213
        return result;
75
213
    }
76
};
77
78
4.15k
Reusable::~Reusable() = default;
79
80
// get missing and include column ids
81
// input include_cids : the output expr slots columns unique ids
82
// missing_cids : the output expr columns that not in row columns cids
83
static void get_missing_and_include_cids(const TabletSchema& schema,
84
                                         const std::vector<SlotDescriptor*>& slots,
85
                                         int target_rs_column_id,
86
                                         std::unordered_set<int>& missing_cids,
87
4.18k
                                         std::unordered_set<int>& include_cids) {
88
4.18k
    missing_cids.clear();
89
4.18k
    include_cids.clear();
90
4.93k
    for (auto* slot : slots) {
91
4.93k
        missing_cids.insert(slot->col_unique_id());
92
4.93k
    }
93
    // insert delete sign column id
94
4.18k
    missing_cids.insert(schema.columns()[schema.delete_sign_idx()]->unique_id());
95
4.18k
    if (target_rs_column_id == -1) {
96
        // no row store columns
97
4.04k
        return;
98
4.04k
    }
99
134
    const TabletColumn& target_rs_column = schema.column_by_uid(target_rs_column_id);
100
134
    DCHECK(target_rs_column.is_row_store_column());
101
    // The full column group is considered a full match, thus no missing cids
102
134
    if (schema.row_columns_uids().empty()) {
103
124
        missing_cids.clear();
104
124
        return;
105
124
    }
106
18
    for (int cid : schema.row_columns_uids()) {
107
18
        missing_cids.erase(cid);
108
18
        include_cids.insert(cid);
109
18
    }
110
10
}
111
112
constexpr static int s_preallocted_blocks_num = 32;
113
114
static void extract_slot_ref(const VExprSPtr& expr, TupleDescriptor* tuple_desc,
115
5.11k
                             std::vector<SlotDescriptor*>& slots) {
116
5.11k
    const auto& children = expr->children();
117
5.11k
    for (const auto& i : children) {
118
192
        extract_slot_ref(i, tuple_desc, slots);
119
192
    }
120
121
5.11k
    auto node_type = expr->node_type();
122
5.11k
    if (node_type == TExprNodeType::SLOT_REF) {
123
4.93k
        int column_id = static_cast<const VSlotRef*>(expr.get())->column_id();
124
4.93k
        auto* slot_desc = tuple_desc->slots()[column_id];
125
4.93k
        slots.push_back(slot_desc);
126
4.93k
    }
127
5.11k
}
128
129
Status Reusable::init(const TDescriptorTable& t_desc_tbl, const std::vector<TExpr>& output_exprs,
130
                      const TQueryOptions& query_options, const TabletSchema& schema,
131
4.18k
                      size_t block_size) {
132
4.18k
    _runtime_state = RuntimeState::create_unique();
133
4.18k
    _runtime_state->set_query_options(query_options);
134
4.18k
    RETURN_IF_ERROR(DescriptorTbl::create(_runtime_state->obj_pool(), t_desc_tbl, &_desc_tbl));
135
4.18k
    _runtime_state->set_desc_tbl(_desc_tbl);
136
90.1k
    for (const auto* slot : tuple_desc()->slots()) {
137
90.1k
        if (!slot->all_access_paths().empty() || !slot->predicate_access_paths().empty()) {
138
0
            return Status::InternalError(
139
0
                    "Short-circuit point query does not support nested column access paths, "
140
0
                    "slot: {}. Please upgrade FE to disable nested column pruning for "
141
0
                    "short-circuit point queries.",
142
0
                    slot->col_name());
143
0
        }
144
90.1k
    }
145
4.18k
    _block_pool.resize(block_size);
146
10.6k
    for (auto& i : _block_pool) {
147
10.6k
        i = Block::create_unique(tuple_desc()->slots(), 2);
148
        // Name is useless but cost space
149
10.6k
        i->clear_names();
150
10.6k
    }
151
152
4.18k
    RETURN_IF_ERROR(VExpr::create_expr_trees(output_exprs, _output_exprs_ctxs));
153
4.18k
    RowDescriptor row_desc(tuple_desc());
154
    // Prepare the exprs to run.
155
4.18k
    RETURN_IF_ERROR(VExpr::prepare(_output_exprs_ctxs, _runtime_state.get(), row_desc));
156
4.18k
    RETURN_IF_ERROR(VExpr::open(_output_exprs_ctxs, _runtime_state.get()));
157
4.18k
    _create_timestamp = butil::gettimeofday_ms();
158
4.18k
    _data_type_serdes = create_data_type_serdes(tuple_desc()->slots());
159
4.18k
    _col_default_values.resize(tuple_desc()->slots().size());
160
4.18k
    bool has_delete_sign = false;
161
94.3k
    for (int i = 0; i < tuple_desc()->slots().size(); ++i) {
162
90.1k
        auto* slot = tuple_desc()->slots()[i];
163
90.1k
        if (slot->col_name() == DELETE_SIGN) {
164
133
            has_delete_sign = true;
165
133
        }
166
90.1k
        _col_uid_to_idx[slot->col_unique_id()] = i;
167
90.1k
        _col_default_values[i] = slot->col_default_value();
168
90.1k
    }
169
170
    // Get the output slot descriptors
171
4.18k
    std::vector<SlotDescriptor*> output_slot_descs;
172
4.92k
    for (const auto& expr : _output_exprs_ctxs) {
173
4.92k
        extract_slot_ref(expr->root(), tuple_desc(), output_slot_descs);
174
4.92k
    }
175
176
    // get the delete sign idx in block
177
4.18k
    if (has_delete_sign) {
178
133
        _delete_sign_idx = _col_uid_to_idx[schema.columns()[schema.delete_sign_idx()]->unique_id()];
179
133
    }
180
181
4.18k
    if (schema.have_column(BeConsts::ROW_STORE_COL)) {
182
134
        const auto& column = *DORIS_TRY(schema.column(BeConsts::ROW_STORE_COL));
183
134
        _row_store_column_ids = column.unique_id();
184
134
    }
185
4.18k
    get_missing_and_include_cids(schema, output_slot_descs, _row_store_column_ids,
186
4.18k
                                 _missing_col_uids, _include_col_uids);
187
188
4.18k
    return Status::OK();
189
4.18k
}
190
191
226
std::unique_ptr<Block> Reusable::get_block() {
192
226
    std::lock_guard lock(_block_mutex);
193
226
    if (_block_pool.empty()) {
194
0
        auto block = Block::create_unique(tuple_desc()->slots(), 2);
195
        // Name is useless but cost space
196
0
        block->clear_names();
197
0
        return block;
198
0
    }
199
226
    auto block = std::move(_block_pool.back());
200
226
    CHECK(block != nullptr);
201
226
    _block_pool.pop_back();
202
226
    return block;
203
226
}
204
205
224
void Reusable::return_block(std::unique_ptr<Block>& block) {
206
224
    std::lock_guard lock(_block_mutex);
207
224
    if (block == nullptr) {
208
0
        return;
209
0
    }
210
224
    block->clear_column_data();
211
224
    _block_pool.push_back(std::move(block));
212
224
    if (_block_pool.size() > s_preallocted_blocks_num) {
213
0
        _block_pool.resize(s_preallocted_blocks_num);
214
0
    }
215
224
}
216
217
229
void Reusable::update_runtime_state(const PTabletKeyLookupRequest& request) {
218
229
    if (request.has_time_zone() && !request.time_zone().empty()) {
219
229
        _runtime_state->set_timezone(request.time_zone());
220
229
    }
221
229
    if (request.has_timestamp_ms()) {
222
229
        _runtime_state->set_query_time(request.timestamp_ms(), request.nano_seconds());
223
229
    }
224
229
}
225
226
7
LookupConnectionCache* LookupConnectionCache::create_global_instance(size_t capacity) {
227
7
    DCHECK(ExecEnv::GetInstance()->get_lookup_connection_cache() == nullptr);
228
7
    auto* res = new LookupConnectionCache(capacity);
229
7
    return res;
230
7
}
231
232
RowCache::RowCache(int64_t capacity, int num_shards)
233
10
        : LRUCachePolicy(CachePolicy::CacheType::POINT_QUERY_ROW_CACHE, capacity,
234
10
                         LRUCacheType::SIZE, config::point_query_row_cache_stale_sweep_time_sec,
235
10
                         num_shards, /*element count capacity */ 0,
236
10
                         /*enable prune*/ true, /*is lru-k*/ true) {}
237
238
// Create global instance of this class
239
7
RowCache* RowCache::create_global_cache(int64_t capacity, uint32_t num_shards) {
240
7
    DCHECK(ExecEnv::GetInstance()->get_row_cache() == nullptr);
241
7
    auto* res = new RowCache(capacity, num_shards);
242
7
    return res;
243
7
}
244
245
0
RowCache* RowCache::instance() {
246
0
    return ExecEnv::GetInstance()->get_row_cache();
247
0
}
248
249
4
bool RowCache::lookup(const RowCacheKey& key, CacheHandle* handle) {
250
4
    const std::string& encoded_key = key.encode();
251
4
    auto* lru_handle = LRUCachePolicy::lookup(encoded_key);
252
4
    if (!lru_handle) {
253
        // cache miss
254
3
        return false;
255
3
    }
256
1
    *handle = CacheHandle(this, lru_handle);
257
1
    return true;
258
4
}
259
260
11
void RowCache::insert(const RowCacheKey& key, const Slice& value) {
261
11
    char* cache_value = static_cast<char*>(malloc(value.size));
262
11
    memcpy(cache_value, value.data, value.size);
263
11
    auto* row_cache_value = new RowCacheValue;
264
11
    row_cache_value->cache_value = cache_value;
265
11
    const std::string& encoded_key = key.encode();
266
11
    auto* handle = LRUCachePolicy::insert(encoded_key, row_cache_value, value.size, value.size,
267
11
                                          CachePriority::NORMAL);
268
    // handle will released
269
11
    auto tmp = CacheHandle {this, handle};
270
11
}
271
272
1
void RowCache::erase(const RowCacheKey& key) {
273
1
    const std::string& encoded_key = key.encode();
274
1
    LRUCachePolicy::erase(encoded_key);
275
1
}
276
277
2.09k
LookupConnectionCache::CacheValue::~CacheValue() {
278
2.09k
    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(
279
2.09k
            ExecEnv::GetInstance()->point_query_executor_mem_tracker());
280
2.09k
    item.reset();
281
2.09k
}
282
283
228
PointQueryExecutor::~PointQueryExecutor() {
284
228
    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(
285
228
            ExecEnv::GetInstance()->point_query_executor_mem_tracker());
286
228
    _tablet.reset();
287
228
    _reusable.reset();
288
228
    _result_block.reset();
289
228
    _row_read_ctxs.clear();
290
228
}
291
292
Status PointQueryExecutor::init(const PTabletKeyLookupRequest* request,
293
229
                                PTabletKeyLookupResponse* response) {
294
229
    SCOPED_TIMER(&_profile_metrics.init_ns);
295
229
    _response = response;
296
    // using cache
297
229
    __int128_t uuid =
298
229
            static_cast<__int128_t>(request->uuid().uuid_high()) << 64 | request->uuid().uuid_low();
299
229
    SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->point_query_executor_mem_tracker());
300
229
    auto cache_handle = LookupConnectionCache::instance()->get(uuid);
301
229
    _binary_row_format = request->is_binary_row();
302
229
    _tablet = DORIS_TRY(ExecEnv::get_tablet(request->tablet_id()));
303
229
    if (cache_handle != nullptr) {
304
93
        _reusable = cache_handle;
305
93
        _profile_metrics.hit_lookup_cache = true;
306
136
    } else {
307
        // Lightweight request: FE may omit reusable query context and rely on uuid cache.
308
        // If cache miss and required parameters are absent, ask FE to resend a full request.
309
136
        if (uuid != 0 && (!request->has_desc_tbl() || request->desc_tbl().empty() ||
310
79
                          !request->has_output_expr() || request->output_expr().empty() ||
311
79
                          !request->has_query_options() || request->query_options().empty())) {
312
2
            if (VLOG_DEBUG_IS_ON) {
313
0
                VLOG_DEBUG << "lookup connection cache miss, ask FE to resend query context"
314
0
                           << ", tablet_id=" << request->tablet_id()
315
0
                           << ", uuid_high=" << request->uuid().uuid_high()
316
0
                           << ", uuid_low=" << request->uuid().uuid_low();
317
0
            }
318
2
            response->set_need_resend_query_context(true);
319
2
            return Status::OK();
320
2
        }
321
134
        if (uuid == 0 && (!request->has_desc_tbl() || request->desc_tbl().empty() ||
322
57
                          !request->has_output_expr() || request->output_expr().empty())) {
323
0
            return Status::InvalidArgument(
324
0
                    "tablet_fetch_data requires desc_tbl/output_expr when uuid is not set");
325
0
        }
326
        // init handle
327
134
        auto reusable_ptr = std::make_shared<Reusable>();
328
134
        TDescriptorTable t_desc_tbl;
329
134
        TExprList t_output_exprs;
330
134
        auto len = cast_set<uint32_t>(request->desc_tbl().size());
331
134
        RETURN_IF_ERROR(
332
134
                deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(request->desc_tbl().data()),
333
134
                                       &len, false, &t_desc_tbl));
334
134
        len = cast_set<uint32_t>(request->output_expr().size());
335
134
        RETURN_IF_ERROR(deserialize_thrift_msg(
336
134
                reinterpret_cast<const uint8_t*>(request->output_expr().data()), &len, false,
337
134
                &t_output_exprs));
338
134
        _reusable = reusable_ptr;
339
134
        TQueryOptions t_query_options;
340
134
        len = cast_set<uint32_t>(request->query_options().size());
341
134
        if (request->has_query_options()) {
342
134
            RETURN_IF_ERROR(deserialize_thrift_msg(
343
134
                    reinterpret_cast<const uint8_t*>(request->query_options().data()), &len, false,
344
134
                    &t_query_options));
345
134
        }
346
134
        if (uuid != 0) {
347
            // could be reused by requests after, pre allocte more blocks
348
77
            RETURN_IF_ERROR(reusable_ptr->init(t_desc_tbl, t_output_exprs.exprs, t_query_options,
349
77
                                               *_tablet->tablet_schema(),
350
77
                                               s_preallocted_blocks_num));
351
77
            LookupConnectionCache::instance()->add(uuid, reusable_ptr);
352
77
        } else {
353
57
            RETURN_IF_ERROR(reusable_ptr->init(t_desc_tbl, t_output_exprs.exprs, t_query_options,
354
57
                                               *_tablet->tablet_schema(), 1));
355
57
        }
356
134
    }
357
227
    _reusable->update_runtime_state(*request);
358
227
    if (request->has_version() && request->version() >= 0) {
359
227
        _version = request->version();
360
227
    }
361
227
    RETURN_IF_ERROR(_init_keys(request));
362
227
    _result_block = _reusable->get_block();
363
227
    CHECK(_result_block != nullptr);
364
365
227
    return Status::OK();
366
227
}
367
368
226
Status PointQueryExecutor::lookup_up() {
369
226
    SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->point_query_executor_mem_tracker());
370
226
    RETURN_IF_ERROR(_lookup_row_key());
371
226
    RETURN_IF_ERROR(_lookup_row_data());
372
223
    RETURN_IF_ERROR(_output_data());
373
223
    return Status::OK();
374
223
}
375
376
224
void PointQueryExecutor::print_profile() {
377
224
    auto init_us = _profile_metrics.init_ns.value() / 1000;
378
224
    auto init_key_us = _profile_metrics.init_key_ns.value() / 1000;
379
224
    auto lookup_key_us = _profile_metrics.lookup_key_ns.value() / 1000;
380
224
    auto lookup_data_us = _profile_metrics.lookup_data_ns.value() / 1000;
381
224
    auto output_data_us = _profile_metrics.output_data_ns.value() / 1000;
382
224
    auto load_segments_key_us = _profile_metrics.load_segment_key_stage_ns.value() / 1000;
383
224
    auto load_segments_data_us = _profile_metrics.load_segment_data_stage_ns.value() / 1000;
384
224
    auto total_us = init_us + lookup_key_us + lookup_data_us + output_data_us;
385
224
    auto read_stats = _profile_metrics.read_stats;
386
224
    const std::string stats_str = fmt::format(
387
224
            "[lookup profile:{}us] init:{}us, init_key:{}us,"
388
224
            " lookup_key:{}us, load_segments_key:{}us, lookup_data:{}us, load_segments_data:{}us,"
389
224
            " output_data:{}us, "
390
224
            "hit_lookup_cache:{}"
391
224
            ", is_binary_row:{}, output_columns:{}, total_keys:{}, row_cache_hits:{}"
392
224
            ", hit_cached_pages:{}, total_pages_read:{}, compressed_bytes_read:{}, "
393
224
            "io_latency:{}ns, "
394
224
            "uncompressed_bytes_read:{}, result_data_bytes:{}, row_hits:{}"
395
224
            ", rs_column_uid:{}, bytes_read_from_local:{}, bytes_read_from_remote:{}, "
396
224
            "local_io_timer:{}, remote_io_timer:{}, local_write_timer:{}",
397
224
            total_us, init_us, init_key_us, lookup_key_us, load_segments_key_us, lookup_data_us,
398
224
            load_segments_data_us, output_data_us, _profile_metrics.hit_lookup_cache,
399
224
            _binary_row_format, _reusable->output_exprs().size(), _row_read_ctxs.size(),
400
224
            _profile_metrics.row_cache_hits, read_stats.cached_pages_num,
401
224
            read_stats.total_pages_num, read_stats.compressed_bytes_read, read_stats.io_ns,
402
224
            read_stats.uncompressed_bytes_read, _profile_metrics.result_data_bytes, _row_hits,
403
224
            _reusable->rs_column_uid(),
404
224
            _profile_metrics.read_stats.file_cache_stats.bytes_read_from_local,
405
224
            _profile_metrics.read_stats.file_cache_stats.bytes_read_from_remote,
406
224
            _profile_metrics.read_stats.file_cache_stats.local_io_timer,
407
224
            _profile_metrics.read_stats.file_cache_stats.remote_io_timer,
408
224
            _profile_metrics.read_stats.file_cache_stats.write_cache_io_timer);
409
410
224
    constexpr static int kSlowThreholdUs = 50 * 1000; // 50ms
411
224
    if (total_us > kSlowThreholdUs) {
412
1
        LOG(WARNING) << "slow query, " << stats_str;
413
223
    } else if (VLOG_DEBUG_IS_ON) {
414
0
        VLOG_DEBUG << stats_str;
415
223
    } else {
416
223
        LOG_EVERY_N(INFO, 1000) << stats_str;
417
223
    }
418
224
}
419
420
227
Status PointQueryExecutor::_init_keys(const PTabletKeyLookupRequest* request) {
421
227
    SCOPED_TIMER(&_profile_metrics.init_key_ns);
422
227
    const auto& schema = _tablet->tablet_schema();
423
    // Point query is only supported on merge-on-write unique key tables.
424
227
    DCHECK(schema->keys_type() == UNIQUE_KEYS && _tablet->enable_unique_key_merge_on_write());
425
227
    if (schema->keys_type() != UNIQUE_KEYS || !_tablet->enable_unique_key_merge_on_write()) {
426
0
        return Status::InvalidArgument(
427
0
                "Point query is only supported on merge-on-write unique key tables, "
428
0
                "tablet_id={}",
429
0
                _tablet->tablet_id());
430
0
    }
431
    // 1. get primary key from conditions
432
227
    _row_read_ctxs.resize(request->key_tuples().size());
433
    // get row cursor and encode keys
434
453
    for (int i = 0; i < request->key_tuples().size(); ++i) {
435
226
        const KeyTuple& key_tuple = request->key_tuples(i);
436
226
        if (UNLIKELY(cast_set<size_t>(key_tuple.key_column_literals_size()) !=
437
226
                     schema->num_key_columns())) {
438
0
            return Status::InvalidArgument(
439
0
                    "Key column count mismatch. expected={}, actual={}, tablet_id={}",
440
0
                    schema->num_key_columns(), key_tuple.key_column_literals_size(),
441
0
                    _tablet->tablet_id());
442
0
        }
443
226
        RowCursor cursor;
444
226
        std::vector<Field> key_fields;
445
226
        key_fields.reserve(key_tuple.key_column_literals_size());
446
1.05k
        for (int j = 0; j < key_tuple.key_column_literals_size(); ++j) {
447
826
            const auto& literal_bytes = key_tuple.key_column_literals(j);
448
826
            TExprNode expr_node;
449
826
            auto len = cast_set<uint32_t>(literal_bytes.size());
450
826
            RETURN_IF_ERROR(
451
826
                    deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(literal_bytes.data()),
452
826
                                           &len, false, &expr_node));
453
826
            const auto& col = schema->column(j);
454
826
            auto data_type = DataTypeFactory::instance().create_data_type(
455
826
                    col.type(), col.precision(), col.frac(), col.length());
456
826
            key_fields.push_back(data_type->get_field(expr_node));
457
826
        }
458
226
        RETURN_IF_ERROR(cursor.init_scan_key(_tablet->tablet_schema(), std::move(key_fields)));
459
226
        cursor.encode_key_with_padding<true>(&_row_read_ctxs[i]._primary_key,
460
226
                                             _tablet->tablet_schema()->num_key_columns(), true);
461
226
    }
462
227
    return Status::OK();
463
227
}
464
465
227
Status PointQueryExecutor::_lookup_row_key() {
466
227
    SCOPED_TIMER(&_profile_metrics.lookup_key_ns);
467
    // 2. lookup row location
468
227
    Status st;
469
227
    if (_version >= 0) {
470
226
        CHECK(config::is_cloud_mode()) << "Only cloud mode support snapshot read at present";
471
226
        SyncOptions options;
472
226
        options.query_version = _version;
473
226
        RETURN_IF_ERROR(std::dynamic_pointer_cast<CloudTablet>(_tablet)->sync_rowsets(options));
474
226
    }
475
227
    std::vector<RowsetSharedPtr> specified_rowsets;
476
227
    {
477
227
        std::shared_lock rlock(_tablet->get_header_lock());
478
227
        specified_rowsets = _tablet->get_rowset_by_ids(nullptr);
479
227
    }
480
227
    std::vector<std::unique_ptr<SegmentCacheHandle>> segment_caches(specified_rowsets.size());
481
450
    for (size_t i = 0; i < _row_read_ctxs.size(); ++i) {
482
223
        RowLocation location;
483
223
        if (!config::disable_storage_row_cache) {
484
0
            RowCache::CacheHandle cache_handle;
485
0
            auto hit_cache = RowCache::instance()->lookup(
486
0
                    {_tablet->tablet_id(), _row_read_ctxs[i]._primary_key}, &cache_handle);
487
0
            if (hit_cache) {
488
0
                _row_read_ctxs[i]._cached_row_data = std::move(cache_handle);
489
0
                ++_profile_metrics.row_cache_hits;
490
0
                continue;
491
0
            }
492
0
        }
493
        // Get rowlocation and rowset, ctx._rowset_ptr will acquire wrap this ptr
494
223
        auto rowset_ptr = std::make_unique<RowsetSharedPtr>();
495
223
        st = (_tablet->lookup_row_key(_row_read_ctxs[i]._primary_key, nullptr, false,
496
223
                                      specified_rowsets, &location, INT32_MAX /*rethink?*/,
497
223
                                      segment_caches, rowset_ptr.get(), false, nullptr,
498
223
                                      &_profile_metrics.read_stats));
499
223
        if (st.is<ErrorCode::KEY_NOT_FOUND>()) {
500
9
            continue;
501
9
        }
502
214
        RETURN_IF_ERROR(st);
503
214
        _row_read_ctxs[i]._row_location = location;
504
        // acquire and wrap this rowset
505
214
        (*rowset_ptr)->acquire();
506
18.4E
        VLOG_DEBUG << "aquire rowset " << (*rowset_ptr)->rowset_id();
507
214
        _row_read_ctxs[i]._rowset_ptr = std::unique_ptr<RowsetSharedPtr, decltype(&release_rowset)>(
508
214
                rowset_ptr.release(), &release_rowset);
509
214
        _row_hits++;
510
214
    }
511
227
    return Status::OK();
512
227
}
513
514
227
Status PointQueryExecutor::_lookup_row_data() {
515
    // 3. get values
516
227
    SCOPED_TIMER(&_profile_metrics.lookup_data_ns);
517
227
    {
518
227
        auto result_columns_guard = _result_block->mutate_columns_scoped();
519
227
        MutableColumns& result_columns = result_columns_guard.mutable_columns();
520
451
        for (size_t i = 0; i < _row_read_ctxs.size(); ++i) {
521
227
            if (_row_read_ctxs[i]._cached_row_data.valid()) {
522
0
                RETURN_IF_ERROR(JsonbSerializeUtil::jsonb_to_columns(
523
0
                        _reusable->get_data_type_serdes(),
524
0
                        _row_read_ctxs[i]._cached_row_data.data().data,
525
0
                        _row_read_ctxs[i]._cached_row_data.data().size,
526
0
                        _reusable->get_col_uid_to_idx(), result_columns,
527
0
                        _reusable->get_col_default_values(), _reusable->include_col_uids()));
528
0
                continue;
529
0
            }
530
227
            if (!_row_read_ctxs[i]._row_location.has_value()) {
531
9
                continue;
532
9
            }
533
218
            std::string value;
534
            // fill block by row store
535
218
            if (_reusable->rs_column_uid() != -1) {
536
218
                bool use_row_cache = !config::disable_storage_row_cache;
537
218
                RETURN_IF_ERROR(_tablet->lookup_row_data(
538
218
                        _row_read_ctxs[i]._primary_key, _row_read_ctxs[i]._row_location.value(),
539
218
                        *(_row_read_ctxs[i]._rowset_ptr), _profile_metrics.read_stats, value,
540
218
                        use_row_cache));
541
                // serialize value to block, currently only jsonb row format
542
218
                RETURN_IF_ERROR(JsonbSerializeUtil::jsonb_to_columns(
543
218
                        _reusable->get_data_type_serdes(), value.data(), value.size(),
544
218
                        _reusable->get_col_uid_to_idx(), result_columns,
545
218
                        _reusable->get_col_default_values(), _reusable->include_col_uids()));
546
218
            }
547
218
            if (!_reusable->missing_col_uids().empty()) {
548
10
                if (!_reusable->runtime_state()->enable_short_circuit_query_access_column_store()) {
549
3
                    std::string missing_columns;
550
22
                    for (int cid : _reusable->missing_col_uids()) {
551
22
                        missing_columns +=
552
22
                                _tablet->tablet_schema()->column_by_uid(cid).name() + ",";
553
22
                    }
554
3
                    return Status::InternalError(
555
3
                            "Not support column store, set store_row_column=true or "
556
3
                            "row_store_columns in table properties, missing columns: " +
557
3
                            missing_columns + " should be added to row store");
558
3
                }
559
                // fill missing columns by column store
560
7
                RowLocation row_loc = _row_read_ctxs[i]._row_location.value();
561
7
                BetaRowsetSharedPtr rowset = std::static_pointer_cast<BetaRowset>(
562
7
                        _tablet->get_rowset(row_loc.rowset_id));
563
7
                SegmentCacheHandle segment_cache;
564
7
                {
565
7
                    SCOPED_TIMER(&_profile_metrics.load_segment_data_stage_ns);
566
7
                    RETURN_IF_ERROR(
567
7
                            SegmentLoader::instance()->load_segments(rowset, &segment_cache, true));
568
7
                }
569
                // find segment
570
7
                auto it = std::find_if(segment_cache.get_segments().cbegin(),
571
7
                                       segment_cache.get_segments().cend(),
572
7
                                       [&](const segment_v2::SegmentSharedPtr& seg) {
573
7
                                           return seg->id() == row_loc.segment_id;
574
7
                                       });
575
7
                const auto& segment = *it;
576
8
                for (int cid : _reusable->missing_col_uids()) {
577
8
                    int pos = _reusable->get_col_uid_to_idx().at(cid);
578
8
                    std::vector<segment_v2::rowid_t> row_ids {
579
8
                            static_cast<segment_v2::rowid_t>(row_loc.row_id)};
580
8
                    auto& column = result_columns[pos];
581
8
                    std::unique_ptr<ColumnIterator> iter;
582
8
                    SlotDescriptor* slot = _reusable->tuple_desc()->slots()[pos];
583
8
                    StorageReadOptions storage_read_options;
584
8
                    storage_read_options.stats = &_read_stats;
585
8
                    storage_read_options.io_ctx.reader_type = ReaderType::READER_QUERY;
586
8
                    RETURN_IF_ERROR(segment->seek_and_read_by_rowid(*_tablet->tablet_schema(), slot,
587
8
                                                                    row_ids, column,
588
8
                                                                    storage_read_options, iter));
589
8
                }
590
7
            }
591
218
        }
592
224
        if (result_columns.size() > _reusable->include_col_uids().size()) {
593
            // Padding rows for some columns that no need to output to mysql client
594
            // eg. SELECT k1,v1,v2 FROM TABLE WHERE k1 = 1, k1 is not in output slots, tuple as bellow
595
            // TupleDescriptor{id=1, tbl=table_with_column_group}
596
            // SlotDescriptor{id=8, col=v1, colUniqueId=1 ...}
597
            // SlotDescriptor{id=9, col=v2, colUniqueId=2 ...}
598
            // thus missing in include_col_uids and missing_col_uids
599
2.15k
            for (auto& column : result_columns) {
600
2.15k
                int padding_rows = _row_hits - cast_set<int>(column->size());
601
2.15k
                if (padding_rows > 0) {
602
1
                    column->insert_many_defaults(padding_rows);
603
1
                }
604
2.15k
            }
605
224
        }
606
224
    }
607
    // filter rows by delete sign
608
224
    if (_row_hits > 0 && _reusable->delete_sign_idx() != -1) {
609
213
        size_t filtered = 0;
610
213
        size_t total = 0;
611
213
        {
612
            // clear_column_data will check reference of ColumnPtr, so we need to release
613
            // reference before clear_column_data
614
213
            ColumnPtr delete_filter_columns =
615
213
                    _result_block->get_columns()[_reusable->delete_sign_idx()];
616
213
            const auto& filter =
617
213
                    assert_cast<const ColumnInt8*>(delete_filter_columns.get())->get_data();
618
213
            filtered = filter.size() - simd::count_zero_num((int8_t*)filter.data(), filter.size());
619
213
            total = filter.size();
620
213
        }
621
622
213
        if (filtered == total) {
623
2
            _result_block->clear_column_data();
624
211
        } else if (filtered > 0) {
625
0
            return Status::NotSupported("Not implemented since only single row at present");
626
0
        }
627
213
    }
628
224
    return Status::OK();
629
224
}
630
631
210
Status serialize_block(std::shared_ptr<TFetchDataResult> res, PTabletKeyLookupResponse* response) {
632
210
    uint8_t* buf = nullptr;
633
210
    uint32_t len = 0;
634
210
    ThriftSerializer ser(false, 4096);
635
210
    RETURN_IF_ERROR(ser.serialize(&(res->result_batch), &len, &buf));
636
210
    response->set_row_batch(std::string((const char*)buf, len));
637
210
    return Status::OK();
638
210
}
639
640
224
Status PointQueryExecutor::_output_data() {
641
    // 4. exprs exec and serialize to mysql row batches
642
224
    SCOPED_TIMER(&_profile_metrics.output_data_ns);
643
224
    if (_result_block->rows()) {
644
213
        RuntimeState state;
645
213
        auto buffer = std::make_shared<PointQueryResultBlockBuffer>(&state);
646
        // TODO reuse mysql_writer
647
213
        VMysqlResultWriter mysql_writer(buffer, _reusable->output_exprs(), nullptr,
648
213
                                        _binary_row_format);
649
213
        RETURN_IF_ERROR(mysql_writer.init(_reusable->runtime_state()));
650
213
        _result_block->clear_names();
651
213
        RETURN_IF_ERROR(mysql_writer.write(_reusable->runtime_state(), *_result_block));
652
213
        RETURN_IF_ERROR(serialize_block(buffer->get_block(), _response));
653
213
        VLOG_DEBUG << "dump block " << _result_block->dump_data();
654
213
    } else {
655
11
        _response->set_empty_batch(true);
656
11
    }
657
224
    _profile_metrics.result_data_bytes = _result_block->bytes();
658
224
    _reusable->return_block(_result_block);
659
224
    return Status::OK();
660
224
}
661
662
} // namespace doris