Coverage Report

Created: 2026-09-16 14:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/jni/jni_reader.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 "jni_reader.h"
19
20
#include <glog/logging.h>
21
22
#include <map>
23
#include <ostream>
24
#include <tuple>
25
#include <unordered_map>
26
#include <utility>
27
28
#include "core/block/block.h"
29
#include "core/types.h"
30
#include "format/jni/jni_data_bridge.h"
31
#include "format/table/partition_column_filler.h"
32
#include "runtime/descriptors.h"
33
#include "runtime/runtime_state.h"
34
#include "util/jni-util.h"
35
36
namespace doris {
37
class RuntimeProfile;
38
class RuntimeState;
39
40
class Block;
41
} // namespace doris
42
43
namespace doris {
44
45
const std::vector<SlotDescriptor*> JniReader::_s_empty_slot_descs;
46
47
// =========================================================================
48
// JniReader constructors
49
// =========================================================================
50
51
JniReader::JniReader(const std::vector<SlotDescriptor*>& file_slot_descs, RuntimeState* state,
52
                     RuntimeProfile* profile, Jni::PluginRef plugin_ref,
53
                     std::map<std::string, std::string> scanner_params,
54
                     std::vector<std::string> column_names, int64_t self_split_weight)
55
2
        : _file_slot_descs(file_slot_descs),
56
2
          _state(state),
57
2
          _profile(profile),
58
2
          _plugin_ref(plugin_ref),
59
2
          _connector_name(plugin_ref.plugin),
60
2
          _scanner_params(std::move(scanner_params)),
61
2
          _column_names(std::move(column_names)),
62
2
          _self_split_weight(static_cast<int32_t>(self_split_weight)) {}
63
64
JniReader::JniReader(Jni::PluginRef plugin_ref, std::map<std::string, std::string> scanner_params)
65
0
        : _file_slot_descs(_s_empty_slot_descs),
66
0
          _plugin_ref(plugin_ref),
67
0
          _connector_name(plugin_ref.plugin),
68
0
          _scanner_params(std::move(scanner_params)) {
69
0
    _is_table_schema = true;
70
0
}
71
72
0
Status JniReader::on_before_init_reader(ReaderInitContext* ctx) {
73
0
    _column_descs = ctx->column_descs;
74
0
    if (_col_name_to_block_idx == nullptr) {
75
0
        _col_name_to_block_idx = ctx->col_name_to_block_idx;
76
0
    }
77
0
    _partition_values.clear();
78
0
    _partition_value_is_null.clear();
79
0
    if (ctx->range == nullptr || ctx->tuple_descriptor == nullptr ||
80
0
        !ctx->range->__isset.columns_from_path_keys) {
81
0
        return Status::OK();
82
0
    }
83
84
0
    DORIS_CHECK(ctx->range->__isset.columns_from_path);
85
0
    DORIS_CHECK(ctx->range->columns_from_path.size() == ctx->range->columns_from_path_keys.size());
86
0
    const bool has_null_flags = ctx->range->__isset.columns_from_path_is_null;
87
0
    if (has_null_flags) {
88
0
        DORIS_CHECK(ctx->range->columns_from_path_is_null.size() ==
89
0
                    ctx->range->columns_from_path_keys.size());
90
0
    }
91
92
0
    std::unordered_map<std::string, const SlotDescriptor*> name_to_slot;
93
0
    for (auto* slot : ctx->tuple_descriptor->slots()) {
94
0
        name_to_slot.emplace(slot->col_name(), slot);
95
0
    }
96
0
    for (size_t i = 0; i < ctx->range->columns_from_path_keys.size(); ++i) {
97
0
        const auto& key = ctx->range->columns_from_path_keys[i];
98
0
        auto slot_it = name_to_slot.find(key);
99
0
        if (slot_it == name_to_slot.end()) {
100
0
            continue;
101
0
        }
102
0
        _partition_values.emplace(
103
0
                key, std::make_tuple(ctx->range->columns_from_path[i], slot_it->second));
104
0
        _partition_value_is_null.emplace(
105
0
                key, has_null_flags ? ctx->range->columns_from_path_is_null[i] : false);
106
0
    }
107
0
    return Status::OK();
108
0
}
109
110
0
Status JniReader::on_after_read_block(Block* block, size_t* read_rows) {
111
0
    if (_column_descs == nullptr || _partition_values.empty() || *read_rows == 0 ||
112
0
        _push_down_agg_type == TPushAggOp::type::COUNT) {
113
0
        return Status::OK();
114
0
    }
115
0
    return _fill_partition_columns(block, *read_rows);
116
0
}
117
118
// =========================================================================
119
// JniReader::open  (merged from JniConnector::open)
120
// =========================================================================
121
122
0
Status JniReader::open(RuntimeState* state, RuntimeProfile* profile) {
123
0
    _state = state;
124
0
    _profile = profile;
125
0
    if (_profile) {
126
0
        ADD_TIMER(_profile, _connector_name.c_str());
127
0
        _open_scanner_time = ADD_CHILD_TIMER(_profile, "OpenScannerTime", _connector_name.c_str());
128
0
        _java_scan_time = ADD_CHILD_TIMER(_profile, "JavaScanTime", _connector_name.c_str());
129
0
        _java_append_data_time =
130
0
                ADD_CHILD_TIMER(_profile, "JavaAppendDataTime", _connector_name.c_str());
131
0
        _java_create_vector_table_time =
132
0
                ADD_CHILD_TIMER(_profile, "JavaCreateVectorTableTime", _connector_name.c_str());
133
0
        _fill_block_time = ADD_CHILD_TIMER(_profile, "FillBlockTime", _connector_name.c_str());
134
0
        _max_time_split_weight_counter = _profile->add_conditition_counter(
135
0
                "MaxTimeSplitWeight", TUnit::UNIT, [](int64_t _c, int64_t c) { return c > _c; },
136
0
                _connector_name.c_str());
137
0
    }
138
0
    _java_scan_watcher = 0;
139
140
0
    JNIEnv* env = nullptr;
141
0
    int batch_size = 0;
142
0
    if (!_is_table_schema && _state) {
143
0
        batch_size = _state->batch_size();
144
0
    }
145
0
    _batch_size = batch_size;
146
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
147
0
    SCOPED_RAW_TIMER(&_jni_scanner_open_watcher);
148
0
    if (_state) {
149
0
        _scanner_params.emplace("time_zone", _state->timezone());
150
0
    }
151
0
    RETURN_IF_ERROR(_init_jni_scanner(env, batch_size));
152
    // Call org.apache.doris.jni.spi.JniScanner#open
153
0
    RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _scanner_api->open).call());
154
155
0
    RETURN_ERROR_IF_EXC(env);
156
0
    _scanner_opened = true;
157
0
    return Status::OK();
158
0
}
159
160
// =========================================================================
161
// JniReader::_do_get_next_block  (merged from JniConnector::get_next_block)
162
// =========================================================================
163
164
0
Status JniReader::_do_get_next_block(Block* block, size_t* read_rows, bool* eof) {
165
0
    JNIEnv* env = nullptr;
166
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
167
0
    long meta_address = 0;
168
0
    {
169
0
        SCOPED_RAW_TIMER(&_java_scan_watcher);
170
0
        RETURN_IF_ERROR(_jni_scanner_obj.call_long_method(env, _scanner_api->get_next_batch_meta)
171
0
                                .call(&meta_address));
172
0
    }
173
0
    if (meta_address == 0) {
174
0
        *read_rows = 0;
175
0
        *eof = true;
176
0
        return Status::OK();
177
0
    }
178
0
    _set_meta(meta_address);
179
0
    long num_rows = _table_meta.next_meta_as_long();
180
0
    if (num_rows == 0) {
181
0
        *read_rows = 0;
182
0
        *eof = true;
183
0
        return Status::OK();
184
0
    }
185
0
    RETURN_IF_ERROR(_fill_block(block, num_rows));
186
0
    *read_rows = num_rows;
187
0
    *eof = false;
188
0
    RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _scanner_api->release_table).call());
189
0
    _has_read += num_rows;
190
0
    return Status::OK();
191
0
}
192
193
// =========================================================================
194
// JniReader::close  (merged from JniConnector::close)
195
// =========================================================================
196
197
0
Status JniReader::close() {
198
0
    if (_closed) {
199
0
        return Status::OK();
200
0
    }
201
0
    if (!_scanner_opened) {
202
0
        _closed = true;
203
0
        return Status::OK();
204
0
    }
205
206
0
    JNIEnv* env = nullptr;
207
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
208
209
    // _fill_block may fail before releasing the current Java table. JniScanner::releaseTable()
210
    // is idempotent, so close always retries it. Java close must still run when that release
211
    // fails, otherwise connector resources such as Paimon's static table-cache lease can leak.
212
0
    auto close_status = _jni_scanner_obj.call_void_method(env, _scanner_api->release_table).call();
213
0
    auto java_close_status = _jni_scanner_obj.call_void_method(env, _scanner_api->close).call();
214
0
    if (close_status.ok() && !java_close_status.ok()) {
215
0
        close_status = std::move(java_close_status);
216
0
    }
217
0
    if (close_status.ok()) {
218
0
        _scanner_opened = false;
219
0
        _closed = true;
220
0
    }
221
0
    return close_status;
222
0
}
223
224
// =========================================================================
225
// JniReader::set_batch_size
226
// =========================================================================
227
228
0
void JniReader::set_batch_size(size_t batch_size) {
229
0
    DCHECK_GT(batch_size, 0);
230
0
    if (_batch_size == batch_size) {
231
0
        return;
232
0
    }
233
0
    _batch_size = batch_size;
234
0
    if (_scanner_opened) {
235
0
        JNIEnv* env = nullptr;
236
0
        Status st = Jni::Env::Get(&env);
237
0
        if (!st) {
238
0
            LOG(WARNING) << "failed to get jni env when set_batch_size: " << st;
239
0
            return;
240
0
        }
241
0
        st = _jni_scanner_obj.call_void_method(env, _scanner_api->set_batch_size)
242
0
                     .with_arg(static_cast<int>(_batch_size))
243
0
                     .call();
244
0
        if (!st) {
245
0
            LOG(WARNING) << "failed to call setBatchSize: " << st;
246
0
        }
247
0
    }
248
0
}
249
250
// =========================================================================
251
// JniReader::_init_jni_scanner  (merged from JniConnector::_init_jni_scanner)
252
// =========================================================================
253
254
0
Status JniReader::_init_jni_scanner(JNIEnv* env, int batch_size) {
255
0
    return Jni::PluginRegistry::create_scanner(env, _plugin_ref, batch_size, _scanner_params,
256
0
                                               &_jni_scanner_obj, &_scanner_api);
257
0
}
258
259
// =========================================================================
260
// JniReader::_fill_block  (merged from JniConnector::_fill_block)
261
// =========================================================================
262
263
0
Status JniReader::_fill_block(Block* block, size_t num_rows) {
264
0
    SCOPED_RAW_TIMER(&_fill_block_watcher);
265
0
    JNIEnv* env = nullptr;
266
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
267
    // Fallback: if _col_name_to_block_idx was not set by the caller (e.g. JdbcScanner),
268
    // build the name-to-position map from the block itself.
269
0
    std::unordered_map<std::string, uint32_t> local_name_to_idx;
270
0
    const std::unordered_map<std::string, uint32_t>* col_map = _col_name_to_block_idx;
271
0
    if (col_map == nullptr) {
272
0
        local_name_to_idx = block->get_name_to_pos_map();
273
0
        col_map = &local_name_to_idx;
274
0
    }
275
0
    for (int i = 0; i < _column_names.size(); ++i) {
276
0
        auto& column_with_type_and_name = block->get_by_position(col_map->at(_column_names[i]));
277
0
        auto& column_ptr = column_with_type_and_name.column;
278
0
        auto& column_type = column_with_type_and_name.type;
279
0
        RETURN_IF_ERROR(JniDataBridge::fill_column(_table_meta, column_ptr, column_type, num_rows));
280
        // Column is not released when fill_column failed. It will be released when releasing table.
281
0
        RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _scanner_api->release_column)
282
0
                                .with_arg(i)
283
0
                                .call());
284
0
        RETURN_ERROR_IF_EXC(env);
285
0
    }
286
0
    return Status::OK();
287
0
}
288
289
0
Status JniReader::_fill_partition_columns(Block* block, size_t num_rows) {
290
0
    std::unordered_map<std::string, uint32_t> local_name_to_idx;
291
0
    const std::unordered_map<std::string, uint32_t>* col_map = _col_name_to_block_idx;
292
0
    if (col_map == nullptr) {
293
0
        local_name_to_idx = block->get_name_to_pos_map();
294
0
        col_map = &local_name_to_idx;
295
0
    }
296
297
0
    for (const auto& desc : *_column_descs) {
298
0
        if (desc.category != ColumnCategory::PARTITION_KEY) {
299
0
            continue;
300
0
        }
301
0
        auto value_it = _partition_values.find(desc.name);
302
0
        if (value_it == _partition_values.end()) {
303
0
            continue;
304
0
        }
305
0
        auto col_it = col_map->find(desc.name);
306
0
        if (col_it == col_map->end()) {
307
0
            return Status::InternalError("Missing partition column {} in block {}", desc.name,
308
0
                                         block->dump_structure());
309
0
        }
310
311
0
        auto& column_with_type_and_name = block->get_by_position(col_it->second);
312
0
        auto mutable_column = std::move(*column_with_type_and_name.column).mutate();
313
0
        const auto& [value, slot_desc] = value_it->second;
314
0
        auto null_it = _partition_value_is_null.find(desc.name);
315
0
        DORIS_CHECK(null_it != _partition_value_is_null.end());
316
0
        RETURN_IF_ERROR(fill_partition_column_from_path_value(*mutable_column, *slot_desc, value,
317
0
                                                              num_rows, null_it->second));
318
0
        column_with_type_and_name.column = std::move(mutable_column);
319
0
    }
320
0
    return Status::OK();
321
0
}
322
323
// =========================================================================
324
// JniReader::_get_statistics  (merged from JniConnector::get_statistics)
325
// =========================================================================
326
327
0
Status JniReader::_get_statistics(JNIEnv* env, std::map<std::string, std::string>* result) {
328
0
    result->clear();
329
0
    Jni::LocalObject metrics;
330
0
    RETURN_IF_ERROR(
331
0
            _jni_scanner_obj.call_object_method(env, _scanner_api->get_statistics).call(&metrics));
332
333
0
    RETURN_IF_ERROR(Jni::Util::convert_to_cpp_map(env, metrics, result));
334
0
    return Status::OK();
335
0
}
336
337
// =========================================================================
338
// JniReader::_collect_profile_before_close
339
// (merged from JniConnector::_collect_profile_before_close)
340
// =========================================================================
341
342
0
void JniReader::_collect_profile_before_close() {
343
0
    if (_scanner_opened && _profile != nullptr) {
344
0
        JNIEnv* env = nullptr;
345
0
        Status st = Jni::Env::Get(&env);
346
0
        if (!st) {
347
0
            LOG(WARNING) << "failed to get jni env when collect profile: " << st;
348
0
            return;
349
0
        }
350
0
        COUNTER_UPDATE(_open_scanner_time, _jni_scanner_open_watcher);
351
0
        COUNTER_UPDATE(_fill_block_time, _fill_block_watcher);
352
353
0
        jlong append_data_time = 0;
354
0
        auto append_time_status =
355
0
                _jni_scanner_obj.call_long_method(env, _scanner_api->get_append_data_time)
356
0
                        .call(&append_data_time);
357
0
        jlong create_vector_table_time = 0;
358
0
        auto create_table_time_status =
359
0
                _jni_scanner_obj.call_long_method(env, _scanner_api->get_create_vector_table_time)
360
0
                        .call(&create_vector_table_time);
361
0
        if (!append_time_status.ok()) {
362
0
            LOG(WARNING) << "failed to collect JNI append-data time before close: "
363
0
                         << append_time_status;
364
0
        }
365
0
        if (!create_table_time_status.ok()) {
366
0
            LOG(WARNING) << "failed to collect JNI vector-table time before close: "
367
0
                         << create_table_time_status;
368
0
        }
369
0
        if (append_time_status.ok() && create_table_time_status.ok()) {
370
0
            COUNTER_UPDATE(_java_append_data_time, append_data_time);
371
0
            COUNTER_UPDATE(_java_create_vector_table_time, create_vector_table_time);
372
0
            COUNTER_UPDATE(_java_scan_time,
373
0
                           _java_scan_watcher - append_data_time - create_vector_table_time);
374
0
            _max_time_split_weight_counter->conditional_update(
375
0
                    _jni_scanner_open_watcher + _fill_block_watcher + _java_scan_watcher,
376
0
                    _self_split_weight);
377
0
        }
378
379
        // update scanner metrics
380
0
        std::map<std::string, std::string> statistics_result;
381
0
        st = _get_statistics(env, &statistics_result);
382
0
        if (!st) {
383
0
            LOG(WARNING) << "failed to get_statistics when collect profile: " << st;
384
0
            return;
385
0
        }
386
387
0
        const auto update_peak = [](int64_t previous, int64_t current) {
388
0
            return current > previous;
389
0
        };
390
0
        for (const auto& metric : statistics_result) {
391
0
            std::vector<std::string> type_and_name = split(metric.first, ":");
392
0
            if (type_and_name.size() != 2) {
393
0
                LOG(WARNING) << "Name of JNI Scanner metric should be pattern like "
394
0
                             << "'metricType:metricName'";
395
0
                continue;
396
0
            }
397
0
            int64_t metric_value = std::stoll(metric.second);
398
0
            RuntimeProfile::Counter* scanner_counter;
399
0
            if (type_and_name[0] == "timer") {
400
0
                scanner_counter =
401
0
                        ADD_CHILD_TIMER(_profile, type_and_name[1], _connector_name.c_str());
402
0
                COUNTER_UPDATE(scanner_counter, metric_value);
403
0
            } else if (type_and_name[0] == "counter") {
404
0
                scanner_counter = ADD_CHILD_COUNTER(_profile, type_and_name[1], TUnit::UNIT,
405
0
                                                    _connector_name.c_str());
406
0
                COUNTER_UPDATE(scanner_counter, metric_value);
407
0
            } else if (type_and_name[0] == "bytes") {
408
0
                scanner_counter = ADD_CHILD_COUNTER(_profile, type_and_name[1], TUnit::BYTES,
409
0
                                                    _connector_name.c_str());
410
0
                COUNTER_UPDATE(scanner_counter, metric_value);
411
0
            } else if (type_and_name[0] == "timer_gauge") {
412
0
                scanner_counter =
413
0
                        ADD_CHILD_TIMER(_profile, type_and_name[1], _connector_name.c_str());
414
0
                COUNTER_SET(scanner_counter, metric_value);
415
0
            } else if (type_and_name[0] == "gauge") {
416
0
                scanner_counter = ADD_CHILD_COUNTER(_profile, type_and_name[1], TUnit::UNIT,
417
0
                                                    _connector_name.c_str());
418
0
                COUNTER_SET(scanner_counter, metric_value);
419
0
            } else if (type_and_name[0] == "bytes_gauge") {
420
0
                scanner_counter = ADD_CHILD_COUNTER(_profile, type_and_name[1], TUnit::BYTES,
421
0
                                                    _connector_name.c_str());
422
0
                COUNTER_SET(scanner_counter, metric_value);
423
0
            } else if (type_and_name[0] == "timer_peak") {
424
0
                auto* scanner_peak_counter = _profile->add_conditition_counter(
425
0
                        type_and_name[1], TUnit::TIME_NS, update_peak, _connector_name.c_str());
426
0
                scanner_peak_counter->conditional_update(metric_value, metric_value);
427
0
            } else if (type_and_name[0] == "peak") {
428
0
                auto* scanner_peak_counter = _profile->add_conditition_counter(
429
0
                        type_and_name[1], TUnit::UNIT, update_peak, _connector_name.c_str());
430
0
                scanner_peak_counter->conditional_update(metric_value, metric_value);
431
0
            } else if (type_and_name[0] == "bytes_peak") {
432
0
                auto* scanner_peak_counter = _profile->add_conditition_counter(
433
0
                        type_and_name[1], TUnit::BYTES, update_peak, _connector_name.c_str());
434
0
                scanner_peak_counter->conditional_update(metric_value, metric_value);
435
0
            } else {
436
                LOG(WARNING) << "Type of JNI Scanner metric should be timer, counter, bytes, "
437
0
                             << "timer_gauge, gauge, bytes_gauge, timer_peak, peak or bytes_peak";
438
0
                continue;
439
0
            }
440
0
        }
441
0
    }
442
0
}
443
444
} // namespace doris