diff --git a/cpp/src/common/device_id.cc b/cpp/src/common/device_id.cc index 02cd32510..b35a8593f 100644 --- a/cpp/src/common/device_id.cc +++ b/cpp/src/common/device_id.cc @@ -209,11 +209,10 @@ std::vector StringArrayDeviceID::split_device_id_string( const std::string& device_id_string) { #ifdef ENABLE_ANTLR4 auto splits = storage::PathNodesGenerator::invokeParser(device_id_string); - return split_device_id_string(splits); #else auto splits = split_string(device_id_string, '.'); - return split_device_id_string(splits); #endif + return split_device_id_string(splits); } std::vector StringArrayDeviceID::split_device_id_string( diff --git a/cpp/src/encoding/dictionary_decoder.h b/cpp/src/encoding/dictionary_decoder.h index 5f64b5873..2962c66ba 100644 --- a/cpp/src/encoding/dictionary_decoder.h +++ b/cpp/src/encoding/dictionary_decoder.h @@ -73,7 +73,8 @@ class DictionaryDecoder : public Decoder { if (entry_index_.empty()) { init_map(buffer); } - int code = value_decoder_.read_int(buffer); + int32_t code = 0; + value_decoder_.read_int(code, buffer); return entry_index_[code]; } diff --git a/cpp/src/encoding/int32_rle_decoder.h b/cpp/src/encoding/int32_rle_decoder.h index aee9048a1..ef7b6f095 100644 --- a/cpp/src/encoding/int32_rle_decoder.h +++ b/cpp/src/encoding/int32_rle_decoder.h @@ -37,6 +37,8 @@ class Int32RleDecoder : public Decoder { int bitpacking_num_; bool is_length_and_bitwidth_readed_; int current_count_; + bool is_rle_run_; + int32_t rle_value_; common::ByteStream byte_cache_{common::MOD_DECODER_OBJ}; int32_t* current_buffer_; Int32Packer* packer_; @@ -49,6 +51,8 @@ class Int32RleDecoder : public Decoder { bitpacking_num_(0), is_length_and_bitwidth_readed_(false), current_count_(0), + is_rle_run_(false), + rle_value_(0), byte_cache_(1024, common::MOD_DECODER_OBJ), current_buffer_(nullptr), packer_(nullptr), @@ -60,13 +64,14 @@ class Int32RleDecoder : public Decoder { } int read_boolean(bool& ret_value, common::ByteStream& in) override { int32_t bool_value; - read_int32(bool_value, in); - ret_value = bool_value == 0 ? false : true; - return common::E_OK; + int ret = read_int32(bool_value, in); + if (ret == common::E_OK) { + ret_value = bool_value != 0; + } + return ret; } int read_int32(int32_t& ret_value, common::ByteStream& in) override { - ret_value = static_cast(read_int(in)); - return common::E_OK; + return read_int(ret_value, in); } int read_int64(int64_t& ret_value, common::ByteStream& in) override { return common::E_TYPE_NOT_MATCH; @@ -89,6 +94,8 @@ class Int32RleDecoder : public Decoder { bit_width_ = 0; bitpacking_num_ = 0; current_count_ = 0; + is_rle_run_ = false; + rle_value_ = 0; } bool has_next(common::ByteStream& buffer) { @@ -103,30 +110,69 @@ class Int32RleDecoder : public Decoder { return current_count_ > 0 || byte_cache_.remaining_size() > 0; } - int32_t read_int(common::ByteStream& buffer) { + int read_int(int32_t& result, common::ByteStream& buffer) { + int ret = common::E_OK; if (!is_length_and_bitwidth_readed_) { // start to reader a new rle+bit-packing pattern - read_length_and_bitwidth(buffer); + if (RET_FAIL(read_length_and_bitwidth(buffer))) { + return ret; + } } if (current_count_ == 0) { - uint8_t header; - int ret = common::E_OK; - if (RET_FAIL( - common::SerializationUtil::read_ui8(header, byte_cache_))) { + // The header is encoded as an unsigned varint where: + // low bit = 0 => RLE run: header_value >> 1 is the run + // count low bit = 1 => bit-packing: header_value >> 1 is the + // group count + uint32_t header_value = 0; + if (RET_FAIL(common::SerializationUtil::read_var_uint( + header_value, byte_cache_))) { return ret; } - call_read_bit_packing_buffer(header); + if (header_value & 1) { + if (RET_FAIL(call_read_bit_packing_buffer(header_value))) { + return ret; + } + } else { + if (RET_FAIL(call_read_rle_run(header_value))) { + return ret; + } + } } --current_count_; - int32_t result = current_buffer_[bitpacking_num_ - current_count_ - 1]; + result = is_rle_run_ + ? rle_value_ + : current_buffer_[bitpacking_num_ - current_count_ - 1]; if (!has_next_package()) { is_length_and_bitwidth_readed_ = false; } - return result; + return ret; } - int call_read_bit_packing_buffer(uint8_t header) { - int bit_packed_group_count = (int)(header >> 1); + int call_read_rle_run(uint32_t header_value) { + int ret = common::E_OK; + int run_length = (int)(header_value >> 1); + if (run_length <= 0) { + return common::E_DECODE_ERR; + } + int byte_width = (bit_width_ + 7) / 8; + // Read the repeated value (stored as byte_width bytes, little-endian) + int32_t value = 0; + for (int i = 0; i < byte_width; i++) { + uint8_t b; + if (RET_FAIL(common::SerializationUtil::read_ui8(b, byte_cache_))) { + return ret; + } + value |= ((int32_t)b) << (i * 8); + } + rle_value_ = value; + is_rle_run_ = true; + current_count_ = run_length; + bitpacking_num_ = run_length; + return ret; + } + + int call_read_bit_packing_buffer(uint32_t header_value) { + int bit_packed_group_count = (int)(header_value >> 1); // in last bit-packing group, there may be some useless value, // lastBitPackedNum indicates how many values is useful uint8_t last_bit_packed_num; @@ -139,6 +185,7 @@ class Int32RleDecoder : public Decoder { current_count_ = (bit_packed_group_count - 1) * 8 + last_bit_packed_num; bitpacking_num_ = current_count_; + is_rle_run_ = false; } else { return common::E_DECODE_ERR; } @@ -236,8 +283,10 @@ class Int32RleDecoder : public Decoder { bitpacking_num_ = 0; is_length_and_bitwidth_readed_ = false; current_count_ = 0; + is_rle_run_ = false; + rle_value_ = 0; if (current_buffer_) { - delete[] current_buffer_; + common::mem_free(current_buffer_); current_buffer_ = nullptr; } if (packer_) { diff --git a/cpp/src/encoding/int32_sprintz_decoder.h b/cpp/src/encoding/int32_sprintz_decoder.h index 3d15597ee..a7c92eede 100644 --- a/cpp/src/encoding/int32_sprintz_decoder.h +++ b/cpp/src/encoding/int32_sprintz_decoder.h @@ -125,7 +125,9 @@ class Int32SprintzDecoder : public SprintzDecoder { decode_size_ = bit_width_ & ~(1 << 7); Int32RleDecoder decoder; for (int i = 0; i < decode_size_; ++i) { - current_buffer_[i] = decoder.read_int(input); + if (RET_FAIL(decoder.read_int(current_buffer_[i], input))) { + return ret; + } } } else { decode_size_ = block_size_ + 1; diff --git a/cpp/src/encoding/int64_rle_decoder.h b/cpp/src/encoding/int64_rle_decoder.h index 8010fe0f7..df8e17838 100644 --- a/cpp/src/encoding/int64_rle_decoder.h +++ b/cpp/src/encoding/int64_rle_decoder.h @@ -37,6 +37,8 @@ class Int64RleDecoder : public Decoder { int bitpacking_num_; bool is_length_and_bitwidth_readed_; int current_count_; + bool is_rle_run_; + int64_t rle_value_; common::ByteStream byte_cache_{common::MOD_DECODER_OBJ}; int64_t* current_buffer_; Int64Packer* packer_; @@ -49,6 +51,8 @@ class Int64RleDecoder : public Decoder { bitpacking_num_(0), is_length_and_bitwidth_readed_(false), current_count_(0), + is_rle_run_(false), + rle_value_(0), byte_cache_(1024, common::MOD_DECODER_OBJ), current_buffer_(nullptr), packer_(nullptr), @@ -65,8 +69,7 @@ class Int64RleDecoder : public Decoder { return common::E_TYPE_NOT_MATCH; } int read_int64(int64_t& ret_value, common::ByteStream& in) override { - ret_value = read_int(in); - return common::E_OK; + return read_int(ret_value, in); } int read_float(float& ret_value, common::ByteStream& in) override { return common::E_TYPE_NOT_MATCH; @@ -86,6 +89,8 @@ class Int64RleDecoder : public Decoder { bit_width_ = 0; bitpacking_num_ = 0; current_count_ = 0; + is_rle_run_ = false; + rle_value_ = 0; } bool has_next(common::ByteStream& buffer) { @@ -100,30 +105,69 @@ class Int64RleDecoder : public Decoder { return current_count_ > 0 || byte_cache_.remaining_size() > 0; } - int64_t read_int(common::ByteStream& buffer) { + int read_int(int64_t& result, common::ByteStream& buffer) { + int ret = common::E_OK; if (!is_length_and_bitwidth_readed_) { // start to reader a new rle+bit-packing pattern - read_length_and_bitwidth(buffer); + if (RET_FAIL(read_length_and_bitwidth(buffer))) { + return ret; + } } if (current_count_ == 0) { - uint8_t header; - int ret = common::E_OK; - if (RET_FAIL( - common::SerializationUtil::read_ui8(header, byte_cache_))) { + // The header is encoded as an unsigned varint where: + // low bit = 0 => RLE run: header_value >> 1 is the run + // count low bit = 1 => bit-packing: header_value >> 1 is the + // group count + uint32_t header_value = 0; + if (RET_FAIL(common::SerializationUtil::read_var_uint( + header_value, byte_cache_))) { return ret; } - call_read_bit_packing_buffer(header); + if (header_value & 1) { + if (RET_FAIL(call_read_bit_packing_buffer(header_value))) { + return ret; + } + } else { + if (RET_FAIL(call_read_rle_run(header_value))) { + return ret; + } + } } --current_count_; - int64_t result = current_buffer_[bitpacking_num_ - current_count_ - 1]; + result = is_rle_run_ + ? rle_value_ + : current_buffer_[bitpacking_num_ - current_count_ - 1]; if (!has_next_package()) { is_length_and_bitwidth_readed_ = false; } - return result; + return ret; } - int call_read_bit_packing_buffer(uint8_t header) { - int bit_packed_group_count = (int)(header >> 1); + int call_read_rle_run(uint32_t header_value) { + int ret = common::E_OK; + int run_length = (int)(header_value >> 1); + if (run_length <= 0) { + return common::E_DECODE_ERR; + } + int byte_width = (bit_width_ + 7) / 8; + // Read the repeated value (stored as byte_width bytes, little-endian) + int64_t value = 0; + for (int i = 0; i < byte_width; i++) { + uint8_t b; + if (RET_FAIL(common::SerializationUtil::read_ui8(b, byte_cache_))) { + return ret; + } + value |= ((int64_t)b) << (i * 8); + } + rle_value_ = value; + is_rle_run_ = true; + current_count_ = run_length; + bitpacking_num_ = run_length; + return ret; + } + + int call_read_bit_packing_buffer(uint32_t header_value) { + int bit_packed_group_count = (int)(header_value >> 1); // in last bit-packing group, there may be some useless value, // lastBitPackedNum indicates how many values is useful uint8_t last_bit_packed_num; @@ -136,25 +180,27 @@ class Int64RleDecoder : public Decoder { current_count_ = (bit_packed_group_count - 1) * 8 + last_bit_packed_num; bitpacking_num_ = current_count_; + is_rle_run_ = false; } else { - printf( - "tsfile-encoding IntRleDecoder: bit_packed_group_count %d, " - "smaller " - "than 1", - bit_packed_group_count); + return common::E_DECODE_ERR; } - read_bit_packing_buffer(bit_packed_group_count, last_bit_packed_num); + ret = read_bit_packing_buffer(bit_packed_group_count, + last_bit_packed_num); return ret; } - void read_bit_packing_buffer(int bit_packed_group_count, - int last_bit_packed_num) { + int read_bit_packing_buffer(int bit_packed_group_count, + int last_bit_packed_num) { + int ret = common::E_OK; if (current_buffer_ != nullptr) { common::mem_free(current_buffer_); } current_buffer_ = static_cast( common::mem_alloc(sizeof(int64_t) * bit_packed_group_count * 8, common::MOD_DECODER_OBJ)); + if (IS_NULL(current_buffer_)) { + return common::E_OOM; + } int bytes_to_read = bit_packed_group_count * bit_width_; if (bytes_to_read > (int)byte_cache_.remaining_size()) { bytes_to_read = byte_cache_.remaining_size(); @@ -162,13 +208,17 @@ class Int64RleDecoder : public Decoder { std::vector bytes(bytes_to_read); for (int i = 0; i < bytes_to_read; i++) { - common::SerializationUtil::read_ui8(bytes[i], byte_cache_); + if (RET_FAIL(common::SerializationUtil::read_ui8(bytes[i], + byte_cache_))) { + return ret; + } } // save all int values in currentBuffer packer_->unpack_all_values( bytes.data(), bytes_to_read, current_buffer_); // decode from bytes, save in currentBuffer + return ret; } int read_length_and_bitwidth(common::ByteStream& buffer) { @@ -177,6 +227,9 @@ class Int64RleDecoder : public Decoder { common::SerializationUtil::read_var_uint(length_, buffer))) { return common::E_PARTIAL_READ; } else { + if (tmp_buf_) { + common::mem_free(tmp_buf_); + } tmp_buf_ = (uint8_t*)common::mem_alloc(length_, common::MOD_DECODER_OBJ); if (tmp_buf_ == nullptr) { @@ -222,6 +275,8 @@ class Int64RleDecoder : public Decoder { bitpacking_num_ = 0; is_length_and_bitwidth_readed_ = false; current_count_ = 0; + is_rle_run_ = false; + rle_value_ = 0; if (current_buffer_) { common::mem_free(current_buffer_); current_buffer_ = nullptr; diff --git a/cpp/src/encoding/int64_sprintz_decoder.h b/cpp/src/encoding/int64_sprintz_decoder.h index a7e3fdd27..7b0827688 100644 --- a/cpp/src/encoding/int64_sprintz_decoder.h +++ b/cpp/src/encoding/int64_sprintz_decoder.h @@ -124,7 +124,9 @@ class Int64SprintzDecoder : public SprintzDecoder { decode_size_ = bit_width_ & ~(1 << 7); Int64RleDecoder decoder; for (int i = 0; i < decode_size_; ++i) { - current_buffer_[i] = decoder.read_int(input); + if (RET_FAIL(decoder.read_int(current_buffer_[i], input))) { + return ret; + } } } else { decode_size_ = block_size_ + 1; diff --git a/cpp/src/file/tsfile_io_reader.cc b/cpp/src/file/tsfile_io_reader.cc index 69e12e45a..31d4a303a 100644 --- a/cpp/src/file/tsfile_io_reader.cc +++ b/cpp/src/file/tsfile_io_reader.cc @@ -300,15 +300,12 @@ int TsFileIOReader::load_device_index_entry( } std::string table_name = device_id_comparable->device_id_->get_table_name(); auto it = tsfile_meta_.table_metadata_index_node_map_.find(table_name); - if (it == tsfile_meta_.table_metadata_index_node_map_.end()) { + if (it == tsfile_meta_.table_metadata_index_node_map_.end() || + it->second == nullptr) { return E_DEVICE_NOT_EXIST; } auto index_node = it->second; - if (index_node == nullptr) { - return E_DEVICE_NOT_EXIST; - } if (index_node->node_type_ == LEAF_DEVICE) { - // FIXME ret = index_node->binary_search_children( device_name, true, device_index_entry, end_offset); } else { diff --git a/cpp/test/encoding/int32_rle_codec_test.cc b/cpp/test/encoding/int32_rle_codec_test.cc index c580a0eb1..dfc737c8b 100644 --- a/cpp/test/encoding/int32_rle_codec_test.cc +++ b/cpp/test/encoding/int32_rle_codec_test.cc @@ -164,4 +164,133 @@ TEST_F(Int32RleEncoderTest, EncodeFlushWithoutData) { EXPECT_EQ(stream.total_size(), 0u); } +// Helper: write a manually crafted RLE segment (Java/Parquet hybrid RLE +// format): +// [length_varint] [bit_width] [group_header_varint] [value_bytes...] +// run_count must be the actual count (written as (run_count<<1)|0 varint). +static void write_rle_segment(common::ByteStream& stream, uint8_t bit_width, + uint32_t run_count, int32_t value) { + common::ByteStream content(32, common::MOD_ENCODER_OBJ); + common::SerializationUtil::write_ui8(bit_width, content); + // Group header: (run_count << 1) | 0 = even varint + common::SerializationUtil::write_var_uint(run_count << 1, content); + // Value: ceil(bit_width / 8) bytes, little-endian + int byte_width = (bit_width + 7) / 8; + uint32_t uvalue = static_cast(value); + for (int i = 0; i < byte_width; i++) { + common::SerializationUtil::write_ui8((uvalue >> (i * 8)) & 0xFF, + content); + } + uint32_t length = content.total_size(); + common::SerializationUtil::write_var_uint(length, stream); + // Append content bytes to stream + uint8_t buf[64]; + uint32_t read_len = 0; + content.read_buf(buf, length, read_len); + stream.write_buf(buf, read_len); +} + +// Regression test: run_count=64 requires a 2-byte LEB128 varint header +// ((64<<1)|0 = 128 = [0x80, 0x01]). Before the fix, only 1 byte was read, +// causing byte misalignment and incorrect decoding. +TEST_F(Int32RleEncoderTest, DecodeRleRunCountExactly64) { + common::ByteStream stream(32, common::MOD_ENCODER_OBJ); + write_rle_segment(stream, /*bit_width=*/7, /*run_count=*/64, + /*value=*/42); + + Int32RleDecoder decoder; + std::vector decoded; + while (decoder.has_next(stream)) { + int32_t v; + decoder.read_int32(v, stream); + decoded.push_back(v); + } + + ASSERT_EQ(decoded.size(), 64u); + for (int32_t v : decoded) { + EXPECT_EQ(v, 42); + } +} + +// Run counts of 128 and 256 each need a 2-byte varint header. +TEST_F(Int32RleEncoderTest, DecodeRleRunCountLarge) { + for (uint32_t count : {128u, 256u, 500u}) { + common::ByteStream stream(64, common::MOD_ENCODER_OBJ); + write_rle_segment(stream, /*bit_width=*/8, /*run_count=*/count, + /*value=*/100); + + Int32RleDecoder decoder; + std::vector decoded; + while (decoder.has_next(stream)) { + int32_t v; + decoder.read_int32(v, stream); + decoded.push_back(v); + } + + ASSERT_EQ(decoded.size(), (size_t)count) + << "Failed for run_count=" << count; + for (int32_t v : decoded) { + EXPECT_EQ(v, 100); + } + } +} + +// Multiple consecutive RLE runs including large ones (simulates real sensor +// data with repeated values and occasional changes). +TEST_F(Int32RleEncoderTest, DecodeMultipleRleRunsWithLargeCount) { + common::ByteStream stream(128, common::MOD_ENCODER_OBJ); + write_rle_segment(stream, /*bit_width=*/8, /*run_count=*/64, + /*value=*/25); + write_rle_segment(stream, /*bit_width=*/8, /*run_count=*/8, + /*value=*/26); + write_rle_segment(stream, /*bit_width=*/8, /*run_count=*/100, + /*value=*/25); + + Int32RleDecoder decoder; + std::vector decoded; + while (decoder.has_next(stream)) { + int32_t v; + decoder.read_int32(v, stream); + decoded.push_back(v); + } + + ASSERT_EQ(decoded.size(), 172u); // 64 + 8 + 100 + for (size_t i = 0; i < 64; i++) EXPECT_EQ(decoded[i], 25); + for (size_t i = 64; i < 72; i++) EXPECT_EQ(decoded[i], 26); + for (size_t i = 72; i < 172; i++) EXPECT_EQ(decoded[i], 25); +} + +// Regression test: Int32RleDecoder::reset() previously called delete[] on +// current_buffer_ which was allocated with mem_alloc (malloc). This is +// undefined behaviour and typically causes a crash. The fix uses mem_free. +TEST_F(Int32RleEncoderTest, ResetAfterDecodeNoCrash) { + common::ByteStream stream(1024, common::MOD_ENCODER_OBJ); + Int32RleEncoder encoder; + for (int i = 0; i < 16; i++) encoder.encode(i, stream); + encoder.flush(stream); + + Int32RleDecoder decoder; + // Decode at least one value to populate current_buffer_ via mem_alloc. + int32_t v; + ASSERT_TRUE(decoder.has_next(stream)); + decoder.read_int32(v, stream); + + // reset() must use mem_free, not delete[]. Before the fix this would crash. + decoder.reset(); + + // Verify the decoder is functional after reset. + common::ByteStream stream2(1024, common::MOD_ENCODER_OBJ); + Int32RleEncoder encoder2; + std::vector input = {7, 7, 7, 7, 7, 7, 7, 7}; + for (int32_t x : input) encoder2.encode(x, stream2); + encoder2.flush(stream2); + + std::vector decoded; + while (decoder.has_next(stream2)) { + decoder.read_int32(v, stream2); + decoded.push_back(v); + } + ASSERT_EQ(decoded, input); +} + } // namespace storage diff --git a/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc b/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc index aa4ff2544..8181b6130 100644 --- a/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc +++ b/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc @@ -24,6 +24,7 @@ #include "common/schema.h" #include "common/tablet.h" #include "file/write_file.h" +#include "reader/result_set.h" #include "reader/tsfile_reader.h" #include "reader/tsfile_tree_reader.h" #include "writer/tsfile_table_writer.h" @@ -425,3 +426,86 @@ TEST_F(TsFileTreeReaderTest, ExtendedRowsAndColumnsTest) { delete measurement; } } + +// Regression test: query_table_on_tree on a device path with three or more +// dot-segments (e.g. "root.sensors.TH") previously SEGVed because: +// 1. StringArrayDeviceID split "root.sensors.TH" into ["root","sensors","TH"] +// instead of the correct ["root.sensors","TH"], so get_table_name() returned +// "root" instead of "root.sensors". +// 2. load_device_index_entry used operator[] on the table map which inserted a +// null entry, then asserted on it. +TEST_F(TsFileTreeReaderTest, QueryTableOnTreeDeepDevicePath) { + TsFileTreeWriter writer(&write_file_); + // Device paths with 3 dot-segments: table_name="root.sensors", device="TH" + std::string device_id = "root.sensors.TH"; + std::string m_temp = "temperature"; + std::string m_humi = "humidity"; + auto* ms_temp = new MeasurementSchema(m_temp, INT32); + auto* ms_humi = new MeasurementSchema(m_humi, INT32); + ASSERT_EQ(E_OK, writer.register_timeseries(device_id, ms_temp)); + ASSERT_EQ(E_OK, writer.register_timeseries(device_id, ms_humi)); + delete ms_temp; + delete ms_humi; + + for (int ts = 0; ts < 5; ts++) { + TsRecord rec(device_id, ts); + rec.add_point(m_temp, static_cast(20 + ts)); + rec.add_point(m_humi, static_cast(50 + ts)); + ASSERT_EQ(E_OK, writer.write(rec)); + } + writer.flush(); + writer.close(); + + TsFileReader reader; + ASSERT_EQ(E_OK, reader.open(file_name_)); + ResultSet* result; + // query_table_on_tree used to SEGV here due to wrong table-name lookup + ASSERT_EQ(E_OK, reader.query_table_on_tree({m_temp, m_humi}, INT64_MIN, + INT64_MAX, result)); + + auto* trs = static_cast(result); + bool has_next = false; + int row_cnt = 0; + while (IS_SUCC(trs->next(has_next)) && has_next) { + row_cnt++; + } + EXPECT_EQ(row_cnt, 5); + reader.destroy_query_data_set(result); + reader.close(); +} + +// Regression test: load_device_index_entry previously used operator[] to look +// up the table node, which silently inserted a null entry and then asserted. +// After the fix it uses find() and returns E_DEVICE_NOT_EXIST gracefully. +// This is triggered when querying a measurement that no device in the file has. +TEST_F(TsFileTreeReaderTest, QueryTableOnTreeMissingMeasurement) { + // Use the same multi-device setup as ReadTreeByTable to ensure a valid + // file. + TsFileTreeWriter writer(&write_file_); + std::vector device_ids = {"root.db1.t1", "root.db2.t1"}; + std::string m_temp = "temperature"; + for (auto dev : device_ids) { + auto* ms = new MeasurementSchema(m_temp, INT32); + ASSERT_EQ(E_OK, writer.register_timeseries(dev, ms)); + delete ms; + TsRecord rec(dev, 0); + rec.add_point(m_temp, static_cast(25)); + ASSERT_EQ(E_OK, writer.write(rec)); + } + writer.flush(); + writer.close(); + + TsFileReader reader; + ASSERT_EQ(E_OK, reader.open(file_name_)); + ResultSet* result = nullptr; + // "nonexistent" is not present in any device. Before the fix, + // load_device_index_entry used operator[] which inserted null and crashed. + // After the fix it returns E_DEVICE_NOT_EXIST or E_COLUMN_NOT_EXIST. + int ret = reader.query_table_on_tree({"nonexistent"}, INT64_MIN, INT64_MAX, + result); + EXPECT_NE(ret, E_OK); // Must not succeed (measurement not found) + if (result != nullptr) { + reader.destroy_query_data_set(result); + } + reader.close(); +}