Coverage Report

Created: 2026-10-20 15:58

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