Coverage Report

Created: 2026-11-02 12:11

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