Coverage Report

Created: 2026-10-30 18:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/index/index_compound_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 "storage/index/index_compound_reader.h"
19
20
#include <CLucene/clucene-config.h>
21
#include <CLucene/debug/error.h>
22
#include <CLucene/debug/mem.h>
23
#include <CLucene/store/RAMDirectory.h>
24
#include <CLucene/util/Misc.h>
25
26
#include <cstdio>
27
#include <cstring>
28
#include <cwchar>
29
#include <memory>
30
#include <utility>
31
32
#include "storage/index/index_fs_directory.h"
33
#include "storage/tablet/tablet_schema.h"
34
#include "util/debug_points.h"
35
#include "util/defer_op.h"
36
37
namespace doris::io {
38
class FileWriter;
39
} // namespace doris::io
40
41
406
#define BUFFER_LENGTH 16384
42
3
#define CL_MAX_PATH 4096
43
44
using FileWriterPtr = std::unique_ptr<doris::io::FileWriter>;
45
46
namespace doris::segment_v2 {
47
namespace {
48
49
// The context of DorisCompoundReader::ScopedIOContext.
50
thread_local const io::IOContext* scoped_io_ctx = nullptr;
51
52
} // namespace
53
54
DorisCompoundReader::ScopedIOContext::ScopedIOContext(const io::IOContext* io_ctx)
55
19
        : _previous(scoped_io_ctx) {
56
19
    scoped_io_ctx = io_ctx;
57
19
}
58
59
19
DorisCompoundReader::ScopedIOContext::~ScopedIOContext() {
60
19
    scoped_io_ctx = _previous;
61
19
}
62
63
/** Implementation of an IndexInput that reads from a portion of the
64
 *  compound file.
65
 */
66
class CSIndexInput : public lucene::store::BufferedIndexInput {
67
private:
68
    CL_NS(store)::IndexInput* base;
69
    std::string file_name;
70
    int64_t fileOffset;
71
    int64_t _length;
72
    const io::IOContext* _io_ctx = nullptr;
73
74
protected:
75
    void readInternal(uint8_t* /*b*/, const int32_t /*len*/) override;
76
6.43M
    void seekInternal(const int64_t /*pos*/) override {}
77
78
public:
79
    CSIndexInput(CL_NS(store)::IndexInput* base, const std::string& file_name,
80
                 const int64_t fileOffset, const int64_t length,
81
                 const int32_t read_buffer_size = CL_NS(store)::BufferedIndexInput::BUFFER_SIZE);
82
    CSIndexInput(const CSIndexInput& clone);
83
    ~CSIndexInput() override;
84
    void close() override;
85
    lucene::store::IndexInput* clone() const override;
86
6.29M
    int64_t length() const override { return _length; }
87
1
    const char* getDirectoryType() const override { return DorisCompoundReader::getClassName(); }
88
17
    const char* getObjectName() const override { return getClassName(); }
89
17
    static const char* getClassName() { return "CSIndexInput"; }
90
    void setIoContext(const void* io_ctx) override;
91
};
92
93
CSIndexInput::CSIndexInput(CL_NS(store)::IndexInput* base, const std::string& file_name,
94
                           const int64_t fileOffset, const int64_t length,
95
                           const int32_t read_buffer_size)
96
14.8k
        : BufferedIndexInput(read_buffer_size) {
97
14.8k
    this->base = base;
98
14.8k
    this->file_name = file_name;
99
14.8k
    this->fileOffset = fileOffset;
100
14.8k
    this->_length = length;
101
14.8k
}
102
103
6.14M
void CSIndexInput::readInternal(uint8_t* b, const int32_t len) {
104
6.14M
    std::lock_guard wlock(((DorisFSDirectory::FSIndexInput*)base)->_this_lock);
105
106
6.14M
    auto start = getFilePointer();
107
6.14M
    if (start + len > _length) {
108
0
        _CLTHROWA(CL_ERR_IO, "read past EOF");
109
0
    }
110
111
    // A stream without a context of its own reads with the thread's scoped one, if any.
112
6.14M
    const io::IOContext* io_ctx = _io_ctx != nullptr ? _io_ctx : scoped_io_ctx;
113
6.14M
    if (io_ctx) {
114
579
        base->setIoContext(io_ctx);
115
579
    }
116
    // The base stream copies the context, statistics pointer included, and outlives the query
117
    // that gave it, so it drops the context even when the read throws.
118
6.14M
    Defer reset_io_ctx {[&]() {
119
6.14M
        if (io_ctx) {
120
579
            base->setIoContext(nullptr);
121
579
        }
122
6.14M
    }};
123
124
6.14M
    DBUG_EXECUTE_IF("CSIndexInput.readInternal", {
125
6.14M
        for (const auto& entry : InvertedIndexDescriptor::index_file_info_map) {
126
6.14M
            if (file_name.find(entry.first) != std::string::npos) {
127
6.14M
                if (!static_cast<const io::IOContext*>(base->getIoContext())->is_index_data) {
128
6.14M
                    _CLTHROWA(CL_ERR_IO,
129
6.14M
                              "The 'is_index_data' flag should be true for inverted index meta "
130
6.14M
                              "files.");
131
6.14M
                }
132
6.14M
            }
133
6.14M
        }
134
6.14M
        for (const auto& entry : InvertedIndexDescriptor::normal_file_info_map) {
135
6.14M
            if (file_name.find(entry.first) != std::string::npos) {
136
6.14M
                if (static_cast<const io::IOContext*>(base->getIoContext())->is_index_data) {
137
6.14M
                    _CLTHROWA(CL_ERR_IO,
138
6.14M
                              "The 'is_index_data' flag should be false for non-meta inverted "
139
6.14M
                              "index files.");
140
6.14M
                }
141
6.14M
            }
142
6.14M
        }
143
6.14M
    });
144
145
6.14M
    base->seek(fileOffset + start);
146
6.14M
    bool read_from_buffer = true;
147
6.14M
    base->readBytes(b, len, read_from_buffer);
148
6.14M
}
149
150
6.38M
CSIndexInput::~CSIndexInput() = default;
151
152
6.36M
lucene::store::IndexInput* CSIndexInput::clone() const {
153
6.36M
    return _CLNEW CSIndexInput(*this);
154
6.36M
}
155
156
6.36M
CSIndexInput::CSIndexInput(const CSIndexInput& clone) : BufferedIndexInput(clone) {
157
6.36M
    this->base = clone.base;
158
6.36M
    this->file_name = clone.file_name;
159
6.36M
    this->fileOffset = clone.fileOffset;
160
6.36M
    this->_length = clone._length;
161
6.36M
}
162
163
19.1k
void CSIndexInput::close() {}
164
165
13.5M
void CSIndexInput::setIoContext(const void* io_ctx) {
166
13.5M
    _io_ctx = static_cast<const io::IOContext*>(io_ctx);
167
13.5M
}
168
169
DorisCompoundReader::DorisCompoundReader(CL_NS(store)::IndexInput* stream,
170
                                         const EntriesType& entries_clone, int32_t read_buffer_size,
171
                                         const io::IOContext* io_ctx)
172
4.94k
        : _stream(stream),
173
4.94k
          _entries(std::make_unique<EntriesType>()),
174
4.94k
          _read_buffer_size(read_buffer_size) {
175
    // After stream clone, the io_ctx needs to be reconfigured.
176
4.94k
    initialize(io_ctx);
177
178
43.2k
    for (const auto& e : entries_clone) {
179
43.2k
        const auto& origin_entry = e.second;
180
43.2k
        auto entry = std::make_unique<ReaderFileEntry>();
181
43.2k
        entry->file_name = origin_entry->file_name;
182
43.2k
        entry->offset = origin_entry->offset;
183
43.2k
        entry->length = origin_entry->length;
184
43.2k
        (*_entries)[e.first] = std::move(entry);
185
43.2k
    }
186
4.94k
};
187
188
DorisCompoundReader::DorisCompoundReader(CL_NS(store)::IndexInput* stream, int32_t read_buffer_size,
189
                                         const io::IOContext* io_ctx)
190
67
        : _ram_dir(std::make_unique<lucene::store::RAMDirectory>()),
191
67
          _stream(stream),
192
67
          _entries(std::make_unique<EntriesType>()),
193
67
          _read_buffer_size(read_buffer_size) {
194
    // After stream clone, the io_ctx needs to be reconfigured.
195
67
    initialize(io_ctx);
196
197
67
    try {
198
67
        int32_t count = _stream->readVInt();
199
67
        uint8_t buffer[BUFFER_LENGTH];
200
486
        for (int32_t i = 0; i < count; i++) {
201
419
            auto entry = std::make_unique<ReaderFileEntry>();
202
            // Read the string length first
203
419
            int32_t string_length = stream->readVInt();
204
            // Allocate appropriate buffer for the string
205
419
            std::wstring tid;
206
419
            tid.resize(string_length);
207
            // Read the string characters directly
208
419
            stream->readChars(tid.data(), 0, string_length);
209
419
            std::string file_name_str(tid.begin(), tid.end());
210
419
            entry->file_name = file_name_str;
211
419
            entry->offset = stream->readLong();
212
419
            entry->length = stream->readLong();
213
419
            VLOG_DEBUG << "string_length:" << string_length << " file_name:" << entry->file_name
214
0
                       << " offset:" << entry->offset << " length:" << entry->length;
215
419
            DBUG_EXECUTE_IF("construct_DorisCompoundReader_failed", {
216
419
                CLuceneError err;
217
419
                err.set(CL_ERR_IO, "construct_DorisCompoundReader_failed");
218
419
                throw err;
219
419
            })
220
            // read header file data
221
419
            if (entry->offset < 0) {
222
                //if offset is -1, it means it's size is lower than DorisFSDirectory::MAX_HEADER_DATA_SIZE, which is 128k.
223
406
                _copyFile(entry->file_name.c_str(), static_cast<int32_t>(entry->length), buffer,
224
406
                          BUFFER_LENGTH);
225
406
            }
226
419
            _entries->emplace(std::move(file_name_str), std::move(entry));
227
419
        }
228
67
    } catch (...) {
229
0
        try {
230
0
            if (_stream != nullptr) {
231
0
                _stream->close();
232
0
                _CLDELETE(_stream)
233
0
            }
234
0
            if (_entries != nullptr) {
235
0
                _entries->clear();
236
0
            }
237
0
            if (_ram_dir) {
238
0
                _ram_dir->close();
239
0
            }
240
0
        } catch (CLuceneError& err) {
241
0
            if (err.number() != CL_ERR_IO) {
242
0
                throw err;
243
0
            }
244
0
        }
245
0
        throw;
246
0
    }
247
67
}
248
249
void DorisCompoundReader::_copyFile(const char* file, int32_t file_length, uint8_t* buffer,
250
408
                                    int32_t buffer_length) {
251
408
    std::unique_ptr<lucene::store::IndexOutput> output(_ram_dir->createOutput(file));
252
408
    int64_t start_ptr = output->getFilePointer();
253
408
    auto remainder = file_length;
254
408
    auto chunk = buffer_length;
255
408
    auto batch_len = file_length < chunk ? file_length : chunk;
256
257
817
    while (remainder > 0) {
258
409
        auto len = remainder < batch_len ? remainder : batch_len;
259
409
        _stream->readBytes(buffer, len);
260
409
        output->writeBytes(buffer, len);
261
409
        remainder -= len;
262
409
    }
263
408
    if (remainder != 0) {
264
0
        TCHAR buf[CL_MAX_PATH + 100];
265
0
        swprintf(buf, CL_MAX_PATH + 100,
266
0
                 _T("Non-zero remainder length after copying")
267
0
                 _T(": %d (id: %s, length: %d, buffer size: %d)"),
268
0
                 (int)remainder, file, (int)file_length, (int)chunk);
269
0
        _CLTHROWT(CL_ERR_IO, buf);
270
0
    }
271
272
408
    int64_t end_ptr = output->getFilePointer();
273
408
    int64_t diff = end_ptr - start_ptr;
274
408
    if (diff != file_length) {
275
0
        TCHAR buf[100];
276
0
        swprintf(buf, 100,
277
0
                 _T("Difference in the output file offsets %d ")
278
0
                 _T("does not match the original file length %d"),
279
0
                 (int)diff, (int)file_length);
280
0
        _CLTHROWA(CL_ERR_IO, buf);
281
0
    }
282
408
    output->close();
283
408
}
284
285
5.00k
DorisCompoundReader::~DorisCompoundReader() {
286
5.00k
    if (!_closed) {
287
3.41k
        try {
288
3.41k
            close();
289
3.41k
        } catch (CLuceneError& err) {
290
0
            LOG(ERROR) << "DorisCompoundReader finalize error:" << err.what();
291
0
        }
292
3.41k
    }
293
5.00k
}
294
295
5
const char* DorisCompoundReader::getClassName() {
296
5
    return "DorisCompoundReader";
297
5
}
298
4
const char* DorisCompoundReader::getObjectName() const {
299
4
    return getClassName();
300
4
}
301
302
3.21k
bool DorisCompoundReader::list(std::vector<std::string>* names) const {
303
3.21k
    if (_closed || _entries == nullptr) {
304
1
        _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed");
305
1
    }
306
29.4k
    for (const auto& entry : *_entries) {
307
29.4k
        names->push_back(entry.first);
308
29.4k
    }
309
3.20k
    return true;
310
3.21k
}
311
312
1.22k
bool DorisCompoundReader::fileExists(const char* name) const {
313
1.22k
    if (_closed || _entries == nullptr) {
314
1
        _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed");
315
1
    }
316
1.22k
    return _entries->find(std::string(name)) != _entries->end();
317
1.22k
}
318
319
1
int64_t DorisCompoundReader::fileModified(const char* name) const {
320
1
    return 0;
321
1
}
322
323
315
int64_t DorisCompoundReader::fileLength(const char* name) const {
324
315
    if (_closed || _entries == nullptr) {
325
1
        _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed");
326
1
    }
327
314
    auto it = _entries->find(std::string(name));
328
314
    if (it == _entries->end()) {
329
1
        char buf[CL_MAX_PATH + 30];
330
1
        strcpy(buf, "File ");
331
1
        strncat(buf, name, CL_MAX_PATH);
332
1
        strcat(buf, " does not exist");
333
1
        _CLTHROWA(CL_ERR_IO, buf);
334
1
    }
335
313
    return it->second->length;
336
314
}
337
338
bool DorisCompoundReader::openInput(const char* name,
339
                                    std::unique_ptr<lucene::store::IndexInput>& ret,
340
43
                                    CLuceneError& error, int32_t bufferSize) {
341
43
    if (_closed || _entries == nullptr) {
342
0
        error.set(CL_ERR_IO, "DorisCompoundReader is already closed");
343
0
        return false;
344
0
    }
345
43
    lucene::store::IndexInput* tmp;
346
43
    bool success = openInput(name, tmp, error, bufferSize);
347
43
    if (success) {
348
42
        ret.reset(tmp);
349
42
    }
350
43
    return success;
351
43
}
352
353
bool DorisCompoundReader::openInput(const char* name, lucene::store::IndexInput*& ret,
354
14.9k
                                    CLuceneError& error, int32_t bufferSize) {
355
14.9k
    if (_stream == nullptr) {
356
1
        error.set(CL_ERR_IO, "Stream closed");
357
1
        return false;
358
1
    }
359
360
14.9k
    auto it = _entries->find(std::string(name));
361
14.9k
    if (it == _entries->end()) {
362
2
        char buf[CL_MAX_PATH + 26];
363
2
        snprintf(buf, CL_MAX_PATH + 26, "No sub-file with id %s found", name);
364
2
        error.set(CL_ERR_IO, buf);
365
2
        return false;
366
2
    }
367
368
14.9k
    const auto& entry = it->second;
369
370
    // If file is in RAM, just return.
371
14.9k
    if (_ram_dir && _ram_dir->fileExists(name)) {
372
161
        return _ram_dir->openInput(name, ret, error, bufferSize);
373
161
    }
374
375
14.8k
    if (bufferSize < 1) {
376
7.04k
        bufferSize = _read_buffer_size;
377
7.04k
    }
378
379
14.8k
    ret = _CLNEW CSIndexInput(_stream, entry->file_name, entry->offset, entry->length, bufferSize);
380
14.8k
    return true;
381
14.9k
}
382
383
5.00k
void DorisCompoundReader::close() {
384
5.00k
    std::lock_guard<std::mutex> wlock(_this_lock);
385
5.00k
    if (_stream != nullptr) {
386
5.00k
        _stream->close();
387
5.00k
        _CLDELETE(_stream)
388
5.00k
    }
389
5.00k
    if (_entries != nullptr) {
390
        // The life cycle of _entries should be consistent with that of the DorisCompoundReader.
391
        // DO NOT DELETE _entries here, it will be deleted in the destructor
392
        // When directory is closed, all _entries are cleared. But the directory may be called in other places.
393
        // If we delete the _entries object here, it will cause core dump.
394
5.00k
        _entries->clear();
395
5.00k
    }
396
5.00k
    if (_ram_dir) {
397
68
        _ram_dir->close();
398
68
    }
399
5.00k
    _closed = true;
400
5.00k
}
401
402
1
bool DorisCompoundReader::doDeleteFile(const char* /*name*/) {
403
1
    _CLTHROWA(CL_ERR_UnsupportedOperation,
404
1
              "UnsupportedOperationException: DorisCompoundReader::doDeleteFile");
405
1
}
406
407
1
void DorisCompoundReader::renameFile(const char* /*from*/, const char* /*to*/) {
408
1
    _CLTHROWA(CL_ERR_UnsupportedOperation,
409
1
              "UnsupportedOperationException: DorisCompoundReader::renameFile");
410
1
}
411
412
1
void DorisCompoundReader::touchFile(const char* /*name*/) {
413
1
    _CLTHROWA(CL_ERR_UnsupportedOperation,
414
1
              "UnsupportedOperationException: DorisCompoundReader::touchFile");
415
1
}
416
417
1
lucene::store::IndexOutput* DorisCompoundReader::createOutput(const char* /*name*/) {
418
1
    _CLTHROWA(CL_ERR_UnsupportedOperation,
419
1
              "UnsupportedOperationException: DorisCompoundReader::createOutput");
420
1
}
421
422
1
std::string DorisCompoundReader::toString() const {
423
1
    return "DorisCompoundReader@";
424
1
}
425
426
241
CL_NS(store)::IndexInput* DorisCompoundReader::getDorisIndexInput() {
427
241
    return _stream;
428
241
}
429
430
5.00k
void DorisCompoundReader::initialize(const io::IOContext* io_ctx) {
431
5.00k
    _stream->setIoContext(io_ctx);
432
5.00k
    _stream->setIdxFileCache(true);
433
5.00k
}
434
435
} // namespace doris::segment_v2