From 5f4bc79a6db4d6cf79eb089a9dabe0e1fc4e12bb Mon Sep 17 00:00:00 2001 From: HUANG Xiangdong <1021782+jixuan1989@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:30:52 +0800 Subject: [PATCH 1/5] Honor floating-point TS_2DIFF precision properties in C++ writers --- cpp/src/encoding/encoder_factory.h | 22 ++ cpp/src/encoding/encoding_props.h | 93 ++++++++ cpp/src/encoding/ts2diff_encoder.h | 12 +- cpp/src/writer/chunk_writer.cc | 11 +- cpp/src/writer/chunk_writer.h | 4 + cpp/src/writer/page_writer.cc | 9 +- cpp/src/writer/page_writer.h | 6 + cpp/src/writer/tsfile_writer.cc | 13 +- cpp/src/writer/value_chunk_writer.cc | 10 +- cpp/src/writer/value_chunk_writer.h | 4 + cpp/src/writer/value_page_writer.cc | 9 +- cpp/src/writer/value_page_writer.h | 5 + cpp/test/encoding/encoding_props_test.cc | 175 ++++++++++++++ .../ts2diff_precision_properties_test.cc | 221 ++++++++++++++++++ 14 files changed, 579 insertions(+), 15 deletions(-) create mode 100644 cpp/src/encoding/encoding_props.h create mode 100644 cpp/test/encoding/encoding_props_test.cc create mode 100644 cpp/test/writer/ts2diff_precision_properties_test.cc diff --git a/cpp/src/encoding/encoder_factory.h b/cpp/src/encoding/encoder_factory.h index 052cbf8eed..d2c54308d7 100644 --- a/cpp/src/encoding/encoder_factory.h +++ b/cpp/src/encoding/encoder_factory.h @@ -28,6 +28,7 @@ #include "encoder.h" #include "encoding/int32_rle_encoder.h" #include "encoding/int64_rle_encoder.h" +#include "encoding_props.h" #include "float_sprintz_encoder.h" #include "gorilla_encoder.h" #include "int32_sprintz_encoder.h" @@ -214,6 +215,27 @@ class EncoderFactory { return nullptr; } + static Encoder* alloc_value_encoder( + common::TSEncoding encoding, common::TSDataType data_type, + const std::map& props) { + Encoder* encoder = alloc_value_encoder(encoding, data_type); + if (encoder == nullptr || props.empty() || + encoding != common::TS_2DIFF || + (data_type != common::FLOAT && data_type != common::DOUBLE)) { + return encoder; + } + const int precision = detail::max_point_number(props); + if (precision == 2) return encoder; // Already the encoder default. + if (data_type == common::FLOAT) { + static_cast(encoder)->set_max_point_number( + precision); + } else { + static_cast(encoder)->set_max_point_number( + precision); + } + return encoder; + } + static void free(Encoder* encoder) { if (encoder == nullptr) { return; diff --git a/cpp/src/encoding/encoding_props.h b/cpp/src/encoding/encoding_props.h new file mode 100644 index 0000000000..3bf86a3124 --- /dev/null +++ b/cpp/src/encoding/encoding_props.h @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * License); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef ENCODING_ENCODING_PROPS_H +#define ENCODING_ENCODING_PROPS_H + +#include +#include +#include + +namespace storage { +namespace detail { + +// Integer.parseInt uses Character.digit(char, 10): BMP decimal digits are +// accepted, but supplementary digits (UTF-16 surrogate pairs) are not. +inline int precision_digit(const std::string& text, size_t& offset) { + const auto first = static_cast(text[offset++]); + if (first < 0x80) { + return first >= '0' && first <= '9' ? first - '0' : -1; + } + int trailing; + uint32_t code; + if (first >= 0xC2 && first <= 0xDF) { + trailing = 1; + code = first & 0x1F; + } else if (first >= 0xE0 && first <= 0xEF) { + trailing = 2; + code = first & 0x0F; + } else { + return -1; + } + if (offset + trailing > text.size()) return -1; + for (int i = 0; i < trailing; ++i) { + const auto next = static_cast(text[offset++]); + if ((next & 0xC0) != 0x80) return -1; + code = (code << 6) | (next & 0x3F); + } + if ((trailing == 2 && code < 0x800) || (code >= 0xD800 && code <= 0xDFFF)) + return -1; + static const uint16_t zeroes[] = { + 0x0660, 0x06F0, 0x07C0, 0x0966, 0x09E6, 0x0A66, 0x0AE6, 0x0B66, 0x0BE6, + 0x0C66, 0x0CE6, 0x0D66, 0x0DE6, 0x0E50, 0x0ED0, 0x0F20, 0x1040, 0x1090, + 0x17E0, 0x1810, 0x1946, 0x19D0, 0x1A80, 0x1A90, 0x1B50, 0x1BB0, 0x1C40, + 0x1C50, 0xA620, 0xA8D0, 0xA900, 0xA9D0, 0xA9F0, 0xAA50, 0xABF0, 0xFF10}; + for (uint16_t zero : zeroes) { + if (code >= zero && code <= zero + 9U) return code - zero; + } + return -1; +} + +inline int max_point_number(const std::map& props) { + const auto property = props.find("max_point_number"); + if (property == props.end()) return 2; + const auto& text = property->second; + size_t offset = 0; + bool negative = false; + if (!text.empty() && (text[0] == '-' || text[0] == '+')) { + negative = text[0] == '-'; + ++offset; + } + // A fresh Java Ts2Diff builder retains zero after NumberFormatException. + if (offset == text.size()) return 0; + const uint32_t limit = negative ? 2147483648U : 2147483647U; + uint32_t value = 0; + while (offset < text.size()) { + const int digit = precision_digit(text, offset); + if (digit < 0 || value > (limit - digit) / 10) return 0; + value = value * 10 + digit; + } + // Negative precision falls back to Java's default floatPrecision (2). + return negative && value != 0 ? 2 : static_cast(value); +} + +} // namespace detail +} // namespace storage + +#endif // ENCODING_ENCODING_PROPS_H diff --git a/cpp/src/encoding/ts2diff_encoder.h b/cpp/src/encoding/ts2diff_encoder.h index de75fd2b73..4231ba9dad 100644 --- a/cpp/src/encoding/ts2diff_encoder.h +++ b/cpp/src/encoding/ts2diff_encoder.h @@ -585,14 +585,16 @@ int TS2DIFFEncoder::encode_batch(const int64_t* values, uint32_t count, // 2^63 rather than (double)INT64_MAX, which rounds up to 2^63 and would // let exactly 2^63 through; the lower limits are exactly representable. template -inline Int java_round(double x) { +FORCE_INLINE Int java_round(double x) { const double r = std::floor(x + 0.5); if (sizeof(Int) < 8) { - if (r > static_cast(std::numeric_limits::max())) { - return std::numeric_limits::max(); + // Include NaN in the existing upper saturation branch: Java rounds + // it to zero. Ordinary values keep the lower-bound fast path. + if (!(r <= static_cast(std::numeric_limits::max()))) { + return std::isnan(r) ? 0 : std::numeric_limits::max(); } - } else if (r >= 9223372036854775808.0) { // 2^63 - return std::numeric_limits::max(); + } else if (!(r < 9223372036854775808.0)) { // 2^63, or NaN + return std::isnan(r) ? 0 : std::numeric_limits::max(); } if (r < static_cast(std::numeric_limits::min())) { return std::numeric_limits::min(); diff --git a/cpp/src/writer/chunk_writer.cc b/cpp/src/writer/chunk_writer.cc index acdb4951db..e671a7647a 100644 --- a/cpp/src/writer/chunk_writer.cc +++ b/cpp/src/writer/chunk_writer.cc @@ -32,12 +32,19 @@ int ChunkWriter::init(const ColumnSchema& col_schema) { int ChunkWriter::init(const std::string& measurement_name, TSDataType data_type, TSEncoding encoding, CompressionType compression_type) { + return init(measurement_name, data_type, encoding, compression_type, + std::map()); +} + +int ChunkWriter::init(const std::string& measurement_name, TSDataType data_type, + TSEncoding encoding, CompressionType compression_type, + const std::map& props) { int ret = E_OK; chunk_statistic_ = StatisticFactory::alloc_statistic(data_type); if (chunk_statistic_ == nullptr) { return E_OOM; - } else if (RET_FAIL( - page_writer_.init(data_type, encoding, compression_type))) { + } else if (RET_FAIL(page_writer_.init(data_type, encoding, compression_type, + props))) { } else if (IS_NULL((first_page_statistic_ = StatisticFactory::alloc_statistic(data_type)))) { ret = E_OOM; diff --git a/cpp/src/writer/chunk_writer.h b/cpp/src/writer/chunk_writer.h index 3d0d6dd32f..03eb3b9a2d 100644 --- a/cpp/src/writer/chunk_writer.h +++ b/cpp/src/writer/chunk_writer.h @@ -59,6 +59,10 @@ class ChunkWriter { int init(const std::string& measurement_name, common::TSDataType data_type, common::TSEncoding encoding, common::CompressionType compression_type); + int init(const std::string& measurement_name, common::TSDataType data_type, + common::TSEncoding encoding, + common::CompressionType compression_type, + const std::map& props); void reset(); void destroy(); diff --git a/cpp/src/writer/page_writer.cc b/cpp/src/writer/page_writer.cc index eebe5b4001..dd9278caff 100644 --- a/cpp/src/writer/page_writer.cc +++ b/cpp/src/writer/page_writer.cc @@ -76,12 +76,19 @@ int PageData::init(ByteStream& time_bs, ByteStream& value_bs, /* ================ PageWriter ================ */ int PageWriter::init(TSDataType data_type, TSEncoding encoding, CompressionType compression) { + return init(data_type, encoding, compression, + std::map()); +} + +int PageWriter::init(TSDataType data_type, TSEncoding encoding, + CompressionType compression, + const std::map& props) { int ret = E_OK; data_type_ = data_type; if (nullptr == (time_encoder_ = EncoderFactory::alloc_time_encoder())) { ret = E_OOM; } else if (nullptr == (value_encoder_ = EncoderFactory::alloc_value_encoder( - encoding, data_type))) { + encoding, data_type, props))) { ret = E_OOM; } else if (nullptr == (statistic_ = StatisticFactory::alloc_statistic(data_type))) { diff --git a/cpp/src/writer/page_writer.h b/cpp/src/writer/page_writer.h index 47c9589135..6e85b0134f 100644 --- a/cpp/src/writer/page_writer.h +++ b/cpp/src/writer/page_writer.h @@ -19,6 +19,9 @@ #ifndef WRITER_PAGE_WRITER_H #define WRITER_PAGE_WRITER_H +#include +#include + #include "common/allocator/byte_stream.h" #include "common/allocator/my_string.h" #include "common/statistic.h" @@ -104,6 +107,9 @@ class PageWriter { is_inited_(false) {} int init(common::TSDataType data_type, common::TSEncoding encoding, common::CompressionType compression); + int init(common::TSDataType data_type, common::TSEncoding encoding, + common::CompressionType compression, + const std::map& props); ~PageWriter() { destroy(); } // reset statistic_, time_out_stream_, value_out_stream_ void reset(); diff --git a/cpp/src/writer/tsfile_writer.cc b/cpp/src/writer/tsfile_writer.cc index ed3e09be70..a354bb41aa 100644 --- a/cpp/src/writer/tsfile_writer.cc +++ b/cpp/src/writer/tsfile_writer.cc @@ -305,6 +305,7 @@ int TsFileWriter::register_aligned_timeseries( MeasurementSchema* ms = new MeasurementSchema( measurement_schema.measurement_name_, measurement_schema.data_type_, measurement_schema.encoding_, measurement_schema.compression_type_); + ms->props_ = measurement_schema.props_; int ret = register_aligned_timeseries(device_id, std::vector{ms}); if (ret != E_OK) { @@ -354,6 +355,7 @@ int TsFileWriter::register_timeseries( MeasurementSchema* ms = new MeasurementSchema( measurement_schema.measurement_name_, measurement_schema.data_type_, measurement_schema.encoding_, measurement_schema.compression_type_); + ms->props_ = measurement_schema.props_; int ret = register_timeseries(device_id, ms); if (ret != E_OK) { delete ms; @@ -533,9 +535,9 @@ int TsFileWriter::do_check_schema( MeasurementSchema* ms = ms_iter->second; if (IS_NULL(ms->chunk_writer_)) { ms->chunk_writer_ = new ChunkWriter; - ret = ms->chunk_writer_->init(ms->measurement_name_, - ms->data_type_, ms->encoding_, - ms->compression_type_); + ret = ms->chunk_writer_->init( + ms->measurement_name_, ms->data_type_, ms->encoding_, + ms->compression_type_, ms->props_); if (IS_SUCC(ret)) { chunk_writers.push_back(ms->chunk_writer_); } else { @@ -569,7 +571,8 @@ int TsFileWriter::ensure_aligned_value_chunk_writer( } int ret = value_chunk_writer->init( measurement_schema->measurement_name_, measurement_schema->data_type_, - measurement_schema->encoding_, measurement_schema->compression_type_); + measurement_schema->encoding_, measurement_schema->compression_type_, + measurement_schema->props_); if (RET_FAIL(ret)) { delete value_chunk_writer; return (ret == E_OOM) ? ret : common::E_INVALID_ARG; @@ -696,7 +699,7 @@ int TsFileWriter::do_check_schema_table( ms->value_chunk_writer_ = new ValueChunkWriter; ret = ms->value_chunk_writer_->init( ms->measurement_name_, ms->data_type_, ms->encoding_, - ms->compression_type_); + ms->compression_type_, ms->props_); if (IS_SUCC(ret)) { value_chunk_writers.push_back(ms->value_chunk_writer_); } else { diff --git a/cpp/src/writer/value_chunk_writer.cc b/cpp/src/writer/value_chunk_writer.cc index 182471c71f..82c9ced74a 100644 --- a/cpp/src/writer/value_chunk_writer.cc +++ b/cpp/src/writer/value_chunk_writer.cc @@ -33,12 +33,20 @@ int ValueChunkWriter::init(const ColumnSchema& col_schema) { int ValueChunkWriter::init(const std::string& measurement_name, TSDataType data_type, TSEncoding encoding, CompressionType compression_type) { + return init(measurement_name, data_type, encoding, compression_type, + std::map()); +} + +int ValueChunkWriter::init(const std::string& measurement_name, + TSDataType data_type, TSEncoding encoding, + CompressionType compression_type, + const std::map& props) { int ret = E_OK; chunk_statistic_ = StatisticFactory::alloc_statistic(data_type); if (chunk_statistic_ == nullptr) { return E_OOM; } else if (RET_FAIL(value_page_writer_.init(data_type, encoding, - compression_type))) { + compression_type, props))) { } else if (IS_NULL((first_page_statistic_ = StatisticFactory::alloc_statistic(data_type)))) { ret = E_OOM; diff --git a/cpp/src/writer/value_chunk_writer.h b/cpp/src/writer/value_chunk_writer.h index 88871c1a96..2348a09d46 100644 --- a/cpp/src/writer/value_chunk_writer.h +++ b/cpp/src/writer/value_chunk_writer.h @@ -59,6 +59,10 @@ class ValueChunkWriter { int init(const std::string& measurement_name, common::TSDataType data_type, common::TSEncoding encoding, common::CompressionType compression_type); + int init(const std::string& measurement_name, common::TSDataType data_type, + common::TSEncoding encoding, + common::CompressionType compression_type, + const std::map& props); void reset(); void destroy(); diff --git a/cpp/src/writer/value_page_writer.cc b/cpp/src/writer/value_page_writer.cc index 7e9b09db73..08a4980b84 100644 --- a/cpp/src/writer/value_page_writer.cc +++ b/cpp/src/writer/value_page_writer.cc @@ -87,10 +87,17 @@ int ValuePageData::init(ByteStream& col_notnull_bitmap_bs, ByteStream& value_bs, int ValuePageWriter::init(TSDataType data_type, TSEncoding encoding, CompressionType compression) { + return init(data_type, encoding, compression, + std::map()); +} + +int ValuePageWriter::init(TSDataType data_type, TSEncoding encoding, + CompressionType compression, + const std::map& props) { int ret = E_OK; data_type_ = data_type; if (nullptr == (value_encoder_ = EncoderFactory::alloc_value_encoder( - encoding, data_type))) { + encoding, data_type, props))) { ret = E_OOM; } else if (nullptr == (statistic_ = StatisticFactory::alloc_statistic(data_type))) { diff --git a/cpp/src/writer/value_page_writer.h b/cpp/src/writer/value_page_writer.h index 61bf59564b..e70cc6586b 100644 --- a/cpp/src/writer/value_page_writer.h +++ b/cpp/src/writer/value_page_writer.h @@ -19,6 +19,8 @@ #ifndef WRITER_PAGE_VALUE_WRITER_H #define WRITER_PAGE_VALUE_WRITER_H +#include +#include #include #include "common/allocator/byte_stream.h" @@ -100,6 +102,9 @@ class ValuePageWriter { ~ValuePageWriter() { destroy(); } int init(common::TSDataType data_type, common::TSEncoding encoding, common::CompressionType compression); + int init(common::TSDataType data_type, common::TSEncoding encoding, + common::CompressionType compression, + const std::map& props); void reset(); void destroy(); diff --git a/cpp/test/encoding/encoding_props_test.cc b/cpp/test/encoding/encoding_props_test.cc new file mode 100644 index 0000000000..a630dbada0 --- /dev/null +++ b/cpp/test/encoding/encoding_props_test.cc @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * License); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include + +#include "encoding/decoder_factory.h" +#include "encoding/encoder_factory.h" + +namespace { +using namespace common; +using namespace storage; + +TEST(EncodingProps, MatchesJavaIntegerParsingAndFallbacks) { + const std::vector> cases{ + {"0", 0}, + {"2", 2}, + {"4", 4}, + {"+4", 4}, + {"0004", 4}, + {"-0", 0}, + {"-4", 2}, + {"-2147483648", 2}, + {"2147483647", 2147483647}, + {"2147483648", 0}, + {"-2147483649", 0}, + {"", 0}, + {"+", 0}, + {"-", 0}, + {" 4", 0}, + {"4 ", 0}, + {"4x", 0}, + {"4.0", 0}, + {"++4", 0}, + {std::string("4\0", 2), 0}, + {"\xD9\xA4", 4}, + {"\xEF\xBC\x94", 4}, + {"+\xD9\xA4", 4}, + {"-\xD9\xA4", 2}, + {"\xE0\xA5\xAA", 4}, + {"\xE1\x81\x84", 4}, + {"\xD9", 0}, + {"\xE0\xA5", 0}, + {"\xE0\x41\x80", 0}, + {"\xE0\x80\xB4", 0}, + {"\xED\xA0\x80", 0}, + {"\xC0\xB4", 0}, + {"\xF0\x9D\x9F\x9C", 0}, + {"\xC2\xA0", 0}, + {"\xE2\x82\xAC", 0}}; + for (const auto& item : cases) { + SCOPED_TRACE(item.first); + EXPECT_EQ(detail::max_point_number({{"max_point_number", item.first}}), + item.second); + } + EXPECT_EQ(detail::max_point_number({}), 2); + EXPECT_EQ(detail::max_point_number({{"unit", "celsius"}}), 2); +} + +TEST(EncodingProps, FactorySetsPagePrecisionAndPreservesItOnReset) { + for (auto type : {FLOAT, DOUBLE}) { + for (int precision : {0, 2, 4}) { + auto* encoder = EncoderFactory::alloc_value_encoder( + TS_2DIFF, type, + {{"max_point_number", std::to_string(precision)}}); + ASSERT_NE(encoder, nullptr); + for (int page = 0; page < 2; ++page) { + ByteStream bytes(64, MOD_DEFAULT); + ASSERT_EQ(type == FLOAT ? encoder->encode(1.23456f, bytes) + : encoder->encode(1.23456, bytes), + E_OK); + ASSERT_EQ(encoder->flush(bytes), E_OK); + uint32_t actual_precision = 99; + ASSERT_EQ( + SerializationUtil::read_var_uint(actual_precision, bytes), + E_OK); + EXPECT_EQ(actual_precision, static_cast(precision)); + encoder->reset(); + } + EncoderFactory::free(encoder); + } + } +} + +TEST(EncodingProps, IgnoresPropertiesForOtherCodecsAndTypes) { + const std::map props{{"max_point_number", "0"}}; + for (auto type : {INT32, INT64, FLOAT, DOUBLE}) { + auto* encoder = EncoderFactory::alloc_value_encoder(PLAIN, type, props); + ASSERT_NE(encoder, nullptr); + EncoderFactory::free(encoder); + } + for (auto type : {INT32, INT64}) { + auto* encoder = + EncoderFactory::alloc_value_encoder(TS_2DIFF, type, props); + ASSERT_NE(encoder, nullptr); + EncoderFactory::free(encoder); + } + EXPECT_EQ(EncoderFactory::alloc_value_encoder(TS_2DIFF, BOOLEAN, props), + nullptr); + for (auto type : {FLOAT, DOUBLE}) { + auto* encoder = EncoderFactory::alloc_value_encoder(TS_2DIFF, type, {}); + ASSERT_NE(encoder, nullptr); + EncoderFactory::free(encoder); + } +} + +TEST(EncodingProps, InfiniteScaleRoundsZeroLikeJava) { + for (auto type : {FLOAT, DOUBLE}) { + auto* encoder = EncoderFactory::alloc_value_encoder( + TS_2DIFF, type, {{"max_point_number", "1000"}}); + ASSERT_NE(encoder, nullptr); + ByteStream bytes(64, MOD_DEFAULT); + for (double value : {0.0, -0.0}) { + ASSERT_EQ(type == FLOAT + ? encoder->encode(static_cast(value), bytes) + : encoder->encode(value, bytes), + E_OK); + } + ASSERT_EQ(encoder->flush(bytes), E_OK); + if (type == FLOAT) { + FloatTS2DIFFDecoder decoder; + for (int i = 0; i < 2; ++i) { + float value = -1; + ASSERT_EQ(decoder.read_float(value, bytes), E_OK); + EXPECT_EQ(value, 0.0f); + EXPECT_FALSE(std::signbit(value)); + } + } else { + DoubleTS2DIFFDecoder decoder; + for (int i = 0; i < 2; ++i) { + double value = -1; + ASSERT_EQ(decoder.read_double(value, bytes), E_OK); + EXPECT_EQ(value, 0.0); + EXPECT_FALSE(std::signbit(value)); + } + } + EncoderFactory::free(encoder); + } +} + +TEST(EncodingProps, JavaRoundingKeepsSaturationAndNaNBehavior) { + const double infinity = std::numeric_limits::infinity(); + const double nan = std::numeric_limits::quiet_NaN(); + EXPECT_EQ(java_round(nan), 0); + EXPECT_EQ(java_round(nan), 0); + EXPECT_EQ(java_round(infinity), + std::numeric_limits::max()); + EXPECT_EQ(java_round(infinity), + std::numeric_limits::max()); + EXPECT_EQ(java_round(-infinity), + std::numeric_limits::min()); + EXPECT_EQ(java_round(-infinity), + std::numeric_limits::min()); + EXPECT_EQ(java_round(1.5), 2); + EXPECT_EQ(java_round(1.5), 2); + EXPECT_EQ(java_round(-1.5), -1); + EXPECT_EQ(java_round(-1.5), -1); +} + +} // namespace diff --git a/cpp/test/writer/ts2diff_precision_properties_test.cc b/cpp/test/writer/ts2diff_precision_properties_test.cc new file mode 100644 index 0000000000..e65018b7e4 --- /dev/null +++ b/cpp/test/writer/ts2diff_precision_properties_test.cc @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * License); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include + +#include +#include +#include +#include + +#include "encoding/ts2diff_decoder.h" +#include "reader/tsfile_reader.h" +#include "writer/tsfile_table_writer.h" +#include "writer/tsfile_writer.h" + +namespace { +using namespace common; +using namespace storage; + +// FLOAT/DOUBLE, aligned/nonaligned, copied/owned schema, tablet/record, +// explicit zero/four/default precision. All calls exist before this fix. +using PrecisionCase = std::tuple; + +class TS2DiffPrecisionProperties + : public testing::TestWithParam { + protected: + std::string path_; + void SetUp() override { + ASSERT_EQ(libtsfile_init(), E_OK); + g_config_value_.time_compress_type_ = UNCOMPRESSED; + ASSERT_EQ(set_page_max_point_count(17), E_OK); + path_ = + testing::TempDir() + "tsfile-precision-" + + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()) + + ".tsfile"; + } + void TearDown() override { + std::remove(path_.c_str()); + libtsfile_destroy(); + } +}; + +TEST_P(TS2DiffPrecisionProperties, KeepsPrecisionAcrossPagesAndChunks) { + const bool is_float = std::get<0>(GetParam()); + const bool aligned = std::get<1>(GetParam()); + const bool copied = std::get<2>(GetParam()); + const bool batch = std::get<3>(GetParam()); + const int precision = std::get<4>(GetParam()); + const auto type = is_float ? FLOAT : DOUBLE; + MeasurementSchema schema("v", type, TS_2DIFF, UNCOMPRESSED); + if (precision >= 0) + schema.props_["max_point_number"] = std::to_string(precision); + { + TsFileWriter writer; + ASSERT_EQ(writer.open(path_), E_OK); + writer.set_generate_table_schema(false); + if (copied) { + ASSERT_EQ(aligned + ? writer.register_aligned_timeseries("root.d", schema) + : writer.register_timeseries("root.d", schema), + E_OK); + } else { + auto* owned = + new MeasurementSchema("v", type, TS_2DIFF, UNCOMPRESSED); + owned->props_ = schema.props_; + std::vector schemas{owned}; + ASSERT_EQ( + aligned ? writer.register_aligned_timeseries("root.d", schemas) + : writer.register_timeseries("root.d", schemas), + E_OK); + } + ASSERT_EQ(writer.get_schema_group_map()->size(), 1U); + EXPECT_EQ(writer.get_schema_group_map() + ->begin() + ->second->measurement_schema_map_.at("v") + ->props_, + schema.props_); + for (int chunk = 0; chunk < 2; ++chunk) { + if (batch) { + auto schemas = + std::make_shared>(); + schemas->emplace_back("v", type, TS_2DIFF, UNCOMPRESSED); + Tablet tablet("root.d", schemas, 260); + for (int row = 0; row < 260; ++row) { + ASSERT_EQ(tablet.add_timestamp(row, chunk * 260 + row), + E_OK); + if (is_float) + ASSERT_EQ(tablet.add_value(row, "v", 1.23456f), E_OK); + else + ASSERT_EQ(tablet.add_value(row, "v", 1.23456), E_OK); + } + ASSERT_EQ(aligned ? writer.write_tablet_aligned(tablet) + : writer.write_tablet(tablet), + E_OK); + } else { + for (int row = 0; row < 260; ++row) { + TsRecord record(chunk * 260 + row, "root.d"); + if (is_float) + record.add_point("v", 1.23456f); + else + record.add_point("v", 1.23456); + ASSERT_EQ(aligned ? writer.write_record_aligned(record) + : writer.write_record(record), + E_OK); + } + } + ASSERT_EQ(writer.flush(), E_OK); + } + ASSERT_EQ(writer.close(), E_OK); + } + TsFileReader reader; + ASSERT_EQ(reader.open(path_), E_OK); + ResultSet* result = nullptr; + std::vector paths{"root.d.v"}; + ASSERT_EQ(reader.query(paths, 0, 520, result), E_OK); + int rows = 0; + std::vector actual; + for (;;) { + bool has_next = false; + ASSERT_EQ(result->next(has_next), E_OK); + if (!has_next) break; + EXPECT_EQ(result->get_value(1), rows); + actual.push_back(is_float ? result->get_value(2) + : result->get_value(2)); + ++rows; + } + EXPECT_EQ(rows, 520); + reader.destroy_query_data_set(result); + ASSERT_EQ(reader.close(), E_OK); + const double expected = precision == 0 ? 1.0 + : precision == 4 ? 1.2346 + : 1.23; + for (double value : actual) { + ASSERT_EQ(value, is_float + ? static_cast(static_cast(expected)) + : expected); + } +} + +class TS2DiffTablePrecision : public testing::Test { + protected: + std::string path_; + void SetUp() override { + ASSERT_EQ(libtsfile_init(), E_OK); + g_config_value_.time_compress_type_ = UNCOMPRESSED; + path_ = + testing::TempDir() + "tsfile-table-precision-" + + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()) + + ".tsfile"; + } + void TearDown() override { + std::remove(path_.c_str()); + libtsfile_destroy(); + } +}; + +TEST_F(TS2DiffTablePrecision, ConfiguresTableValueChunk) { + auto* column = new MeasurementSchema("v", FLOAT, TS_2DIFF, UNCOMPRESSED); + column->props_["max_point_number"] = "4"; + auto schema = std::make_shared( + "audit", std::vector{column}, + std::vector{ColumnCategory::FIELD}); + TsFileWriter writer; + ASSERT_EQ(writer.open(path_), E_OK); + ASSERT_EQ(writer.register_table(schema), E_OK); + Tablet tablet("audit", schema->get_measurement_names(), + schema->get_data_types(), schema->get_column_categories(), 1); + ASSERT_EQ(tablet.add_timestamp(0, 1), E_OK); + ASSERT_EQ(tablet.add_value(0, "v", 1.23456f), E_OK); + ASSERT_EQ(writer.write_table(tablet), E_OK); + auto* chunk = writer.get_schema_group_map() + ->begin() + ->second->measurement_schema_map_.at("v") + ->value_chunk_writer_; + ASSERT_NE(chunk, nullptr); + ASSERT_EQ(chunk->end_encode_chunk(), E_OK); + std::vector bytes(chunk->get_chunk_data().total_size()); + ASSERT_EQ( + copy_bs_to_buf(chunk->get_chunk_data(), bytes.data(), bytes.size()), + E_OK); + ByteStream page; + page.wrap_from(bytes.data(), bytes.size()); + PageHeader header; + ASSERT_EQ(header.deserialize_from(page, false, FLOAT), E_OK); + uint32_t rows = 0; + uint8_t bitmap = 0; + ASSERT_EQ(SerializationUtil::read_ui32(rows, page), E_OK); + ASSERT_EQ(SerializationUtil::read_ui8(bitmap, page), E_OK); + EXPECT_EQ(rows, 1U); + EXPECT_EQ(bitmap, 0x80); + FloatTS2DIFFDecoder decoder; + float value = 0; + ASSERT_EQ(decoder.read_float(value, page), E_OK); + EXPECT_EQ(value, 1.2346f); + ASSERT_EQ(writer.close(), E_OK); +} + +INSTANTIATE_TEST_SUITE_P(WriterPaths, TS2DiffPrecisionProperties, + testing::Combine(testing::Bool(), testing::Bool(), + testing::Bool(), testing::Bool(), + testing::Values(0, 4, -1))); + +} // namespace From f933c9eaa1dd1adc399c0b1f59fe99e87a93ef66 Mon Sep 17 00:00:00 2001 From: HUANG Xiangdong <1021782+jixuan1989@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:50:45 +0800 Subject: [PATCH 2/5] Escape reserved TS_2DIFF precisions for zero-only pages --- cpp/src/encoding/ts2diff_encoder.h | 13 ++- cpp/test/encoding/encoding_props_test.cc | 84 +++++++++++++++++++ .../ts2diff_precision_properties_test.cc | 21 +++-- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/cpp/src/encoding/ts2diff_encoder.h b/cpp/src/encoding/ts2diff_encoder.h index 4231ba9dad..2e1546c9d9 100644 --- a/cpp/src/encoding/ts2diff_encoder.h +++ b/cpp/src/encoding/ts2diff_encoder.h @@ -889,7 +889,13 @@ FORCE_INLINE int FloatTS2DIFFEncoder::flush(common::ByteStream& out_stream) { return common::E_OK; } const int num_values = static_cast(underflow_flags_.size()); - if (has_overflow()) { + // The two largest precisions collide with the leading overflow tags. + // Always wrap them, even for all-scaled zeroes, so the actual precision + // appears after the bitmap where both Java and C++ read it as a value. + if (has_overflow() || + max_point_number_ >= + static_cast( + ts2diff_java_detail::FLAG_ORIGINAL_VALUE_OVERFLOW)) { std::vector scaled_bitmap( static_cast(num_values / 8 + 1), 0); std::vector raw_bits_bitmap( @@ -959,7 +965,10 @@ FORCE_INLINE int DoubleTS2DIFFEncoder::flush(common::ByteStream& out_stream) { return common::E_OK; } const int num_values = static_cast(underflow_flags_.size()); - if (has_overflow()) { + if (has_overflow() || + max_point_number_ >= + static_cast( + ts2diff_java_detail::FLAG_ORIGINAL_VALUE_OVERFLOW)) { std::vector scaled_bitmap( static_cast(num_values / 8 + 1), 0); std::vector raw_bits_bitmap( diff --git a/cpp/test/encoding/encoding_props_test.cc b/cpp/test/encoding/encoding_props_test.cc index a630dbada0..6b38920abc 100644 --- a/cpp/test/encoding/encoding_props_test.cc +++ b/cpp/test/encoding/encoding_props_test.cc @@ -153,6 +153,90 @@ TEST(EncodingProps, InfiniteScaleRoundsZeroLikeJava) { } } +TEST(EncodingProps, ZeroOnlyPagesEscapeReservedPrecisionMarkers) { + for (auto type : {FLOAT, DOUBLE}) { + for (int precision : {1000, 2147483645, 2147483646, 2147483647}) { + auto* encoder = EncoderFactory::alloc_value_encoder( + TS_2DIFF, type, + {{"max_point_number", std::to_string(precision)}}); + ASSERT_NE(encoder, nullptr); + for (int rows : {1, 8, 129, 260}) { + SCOPED_TRACE(std::to_string(type) + ":" + + std::to_string(precision) + ":" + + std::to_string(rows)); + for (int page = 0; page < 2; ++page) { + ByteStream bytes(64, MOD_DEFAULT); + ASSERT_EQ(encoder->flush(bytes), E_OK); + EXPECT_EQ(bytes.total_size(), 0U); + for (int row = 0; row < rows; ++row) { + const double zero = row % 2 == 0 ? 0.0 : -0.0; + ASSERT_EQ(type == FLOAT + ? encoder->encode( + static_cast(zero), bytes) + : encoder->encode(zero, bytes), + E_OK); + } + ASSERT_EQ(encoder->flush(bytes), E_OK); + std::vector copy(bytes.total_size()); + ASSERT_EQ(copy_bs_to_buf(bytes, copy.data(), copy.size()), + E_OK); + ByteStream header; + header.wrap_from(copy.data(), copy.size()); + uint32_t tag = 0; + ASSERT_EQ(SerializationUtil::read_var_uint(tag, header), + E_OK); + EXPECT_EQ(tag, precision >= 2147483646 + ? 2147483647U + : static_cast(precision)); + if (precision >= 2147483646) { + uint32_t count = 0; + ASSERT_EQ( + SerializationUtil::read_var_uint(count, header), + E_OK); + EXPECT_EQ(count, static_cast(rows)); + for (int i = 0; i < rows / 8 + 1; ++i) { + uint8_t bitmap = 0; + ASSERT_EQ( + SerializationUtil::read_ui8(bitmap, header), + E_OK); + const int remaining = rows - i * 8; + EXPECT_EQ(bitmap, remaining >= 8 + ? 255U + : (1U << remaining) - 1); + } + uint32_t stored_precision = 0; + ASSERT_EQ(SerializationUtil::read_var_uint( + stored_precision, header), + E_OK); + EXPECT_EQ(stored_precision, + static_cast(precision)); + } + auto* decoder = + DecoderFactory::alloc_value_decoder(TS_2DIFF, type); + ASSERT_NE(decoder, nullptr); + for (int row = 0; row < rows; ++row) { + double actual = -1; + if (type == FLOAT) { + float value = -1; + ASSERT_EQ(decoder->read_float(value, bytes), E_OK); + actual = value; + } else { + ASSERT_EQ(decoder->read_double(actual, bytes), + E_OK); + } + EXPECT_EQ(actual, 0.0); + EXPECT_FALSE(std::signbit(actual)); + } + EXPECT_FALSE(decoder->has_remaining(bytes)); + DecoderFactory::free(decoder); + encoder->reset(); + } + } + EncoderFactory::free(encoder); + } + } +} + TEST(EncodingProps, JavaRoundingKeepsSaturationAndNaNBehavior) { const double infinity = std::numeric_limits::infinity(); const double nan = std::numeric_limits::quiet_NaN(); diff --git a/cpp/test/writer/ts2diff_precision_properties_test.cc b/cpp/test/writer/ts2diff_precision_properties_test.cc index e65018b7e4..5192bb3cfb 100644 --- a/cpp/test/writer/ts2diff_precision_properties_test.cc +++ b/cpp/test/writer/ts2diff_precision_properties_test.cc @@ -63,6 +63,7 @@ TEST_P(TS2DiffPrecisionProperties, KeepsPrecisionAcrossPagesAndChunks) { const bool copied = std::get<2>(GetParam()); const bool batch = std::get<3>(GetParam()); const int precision = std::get<4>(GetParam()); + const double input = precision >= 2147483646 ? 0.0 : 1.23456; const auto type = is_float ? FLOAT : DOUBLE; MeasurementSchema schema("v", type, TS_2DIFF, UNCOMPRESSED); if (precision >= 0) @@ -102,9 +103,11 @@ TEST_P(TS2DiffPrecisionProperties, KeepsPrecisionAcrossPagesAndChunks) { ASSERT_EQ(tablet.add_timestamp(row, chunk * 260 + row), E_OK); if (is_float) - ASSERT_EQ(tablet.add_value(row, "v", 1.23456f), E_OK); + ASSERT_EQ(tablet.add_value(row, "v", + static_cast(input)), + E_OK); else - ASSERT_EQ(tablet.add_value(row, "v", 1.23456), E_OK); + ASSERT_EQ(tablet.add_value(row, "v", input), E_OK); } ASSERT_EQ(aligned ? writer.write_tablet_aligned(tablet) : writer.write_tablet(tablet), @@ -113,9 +116,9 @@ TEST_P(TS2DiffPrecisionProperties, KeepsPrecisionAcrossPagesAndChunks) { for (int row = 0; row < 260; ++row) { TsRecord record(chunk * 260 + row, "root.d"); if (is_float) - record.add_point("v", 1.23456f); + record.add_point("v", static_cast(input)); else - record.add_point("v", 1.23456); + record.add_point("v", input); ASSERT_EQ(aligned ? writer.write_record_aligned(record) : writer.write_record(record), E_OK); @@ -144,9 +147,10 @@ TEST_P(TS2DiffPrecisionProperties, KeepsPrecisionAcrossPagesAndChunks) { EXPECT_EQ(rows, 520); reader.destroy_query_data_set(result); ASSERT_EQ(reader.close(), E_OK); - const double expected = precision == 0 ? 1.0 - : precision == 4 ? 1.2346 - : 1.23; + const double expected = precision >= 2147483646 ? 0.0 + : precision == 0 ? 1.0 + : precision == 4 ? 1.2346 + : 1.23; for (double value : actual) { ASSERT_EQ(value, is_float ? static_cast(static_cast(expected)) @@ -216,6 +220,7 @@ TEST_F(TS2DiffTablePrecision, ConfiguresTableValueChunk) { INSTANTIATE_TEST_SUITE_P(WriterPaths, TS2DiffPrecisionProperties, testing::Combine(testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), - testing::Values(0, 4, -1))); + testing::Values(0, 4, -1, 2147483646, + 2147483647))); } // namespace From 861aa8ce8aa1c01d014f95ee21a2420c3e6199d8 Mon Sep 17 00:00:00 2001 From: HUANG Xiangdong <1021782+jixuan1989@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:14:15 +0800 Subject: [PATCH 3/5] Match Java rounding at floating-point boundaries --- cpp/src/encoding/ts2diff_encoder.h | 51 ++++++++++++++---------- cpp/test/encoding/encoding_props_test.cc | 32 +++++++++++++++ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/cpp/src/encoding/ts2diff_encoder.h b/cpp/src/encoding/ts2diff_encoder.h index 2e1546c9d9..2575d71510 100644 --- a/cpp/src/encoding/ts2diff_encoder.h +++ b/cpp/src/encoding/ts2diff_encoder.h @@ -578,28 +578,39 @@ int TS2DIFFEncoder::encode_batch(const int64_t* values, uint32_t count, // Java Math.round(x) == floor(x + 0.5) (ties go towards +infinity), // unlike std::lround's ties-away-from-zero (-2.5 -> -2, not -3). -// Math.round saturates at the integer limits; a plain static_cast of the -// boundary value (e.g. INT64_MAX rounding up from below, or 2^63 passing -// the <= (double)INT64_MAX gate) would be undefined behaviour, so the -// conversion clamps first. The 64-bit upper bound is compared against -// 2^63 rather than (double)INT64_MAX, which rounds up to 2^63 and would -// let exactly 2^63 through; the lower limits are exactly representable. +// Round the IEEE significand using integer arithmetic: ties go toward +// positive infinity, NaN becomes zero, and out-of-range results saturate. +// Adding 0.5 before floor can itself round (just below 0.5, or above 2^52). template FORCE_INLINE Int java_round(double x) { - const double r = std::floor(x + 0.5); - if (sizeof(Int) < 8) { - // Include NaN in the existing upper saturation branch: Java rounds - // it to zero. Ordinary values keep the lower-bound fast path. - if (!(r <= static_cast(std::numeric_limits::max()))) { - return std::isnan(r) ? 0 : std::numeric_limits::max(); - } - } else if (!(r < 9223372036854775808.0)) { // 2^63, or NaN - return std::isnan(r) ? 0 : std::numeric_limits::max(); + const uint64_t bits = static_cast(common::double_to_long(x)); + const uint64_t fraction = bits & 0x000FFFFFFFFFFFFFULL; + const unsigned exponent = static_cast((bits >> 52) & 0x7FF); + const bool negative = (bits >> 63) != 0; + if (exponent < 1022) return 0; // Magnitude below one half. + if (exponent >= 1023 + std::numeric_limits::digits) { + if (exponent == 0x7FF && fraction != 0) return 0; + return negative ? std::numeric_limits::min() + : std::numeric_limits::max(); + } + const uint64_t significand = fraction | (uint64_t{1} << 52); + const int shift = 1075 - static_cast(exponent); + uint64_t rounded = 0; + if (shift > 0) { + const uint64_t remainder = significand & ((uint64_t{1} << shift) - 1); + const uint64_t threshold = (uint64_t{1} << (shift - 1)) + negative; + rounded = (significand >> shift) + (remainder >= threshold); + } else { + // Here the value is already integral; the range gate bounds this + // left shift to 0..10 for int64_t (unreachable for int32_t). + rounded = significand << -shift; } - if (r < static_cast(std::numeric_limits::min())) { - return std::numeric_limits::min(); + if (rounded > static_cast(std::numeric_limits::max())) { + return negative ? std::numeric_limits::min() + : std::numeric_limits::max(); } - return static_cast(r); + const Int result = static_cast(rounded); + return negative ? -result : result; } class FloatTS2DIFFEncoder : public TS2DIFFEncoder { @@ -637,7 +648,7 @@ class FloatTS2DIFFEncoder : public TS2DIFFEncoder { int encode(double value, common::ByteStream& out_stream); private: - int32_t convert_float_to_int(float value) { + FORCE_INLINE int32_t convert_float_to_int(float value) { const double scaled = static_cast(value) * max_point_value_; if (scaled > static_cast(std::numeric_limits::max()) || scaled < static_cast(std::numeric_limits::min())) { @@ -713,7 +724,7 @@ class DoubleTS2DIFFEncoder : public TS2DIFFEncoder { int encode(double value, common::ByteStream& out_stream); private: - int64_t convert_double_to_long(double value) { + FORCE_INLINE int64_t convert_double_to_long(double value) { const double scaled = value * max_point_value_; if (scaled > static_cast(std::numeric_limits::max()) || scaled < static_cast(std::numeric_limits::min())) { diff --git a/cpp/test/encoding/encoding_props_test.cc b/cpp/test/encoding/encoding_props_test.cc index 6b38920abc..3b059cdc46 100644 --- a/cpp/test/encoding/encoding_props_test.cc +++ b/cpp/test/encoding/encoding_props_test.cc @@ -256,4 +256,36 @@ TEST(EncodingProps, JavaRoundingKeepsSaturationAndNaNBehavior) { EXPECT_EQ(java_round(-1.5), -1); } +TEST(EncodingProps, JavaRoundingPreservesHalfNeighborsAndLargeIntegers) { + const std::vector> cases{ + {std::nextafter(0.5, 0.0), 0}, + {0.5, 1}, + {std::nextafter(0.5, 1.0), 1}, + {std::nextafter(1.5, 1.0), 1}, + {1.5, 2}, + {std::nextafter(1.5, 2.0), 2}, + {std::nextafter(-0.5, -1.0), -1}, + {-0.5, 0}, + {std::nextafter(-0.5, 0.0), 0}, + {std::nextafter(-1.5, -2.0), -2}, + {-1.5, -1}, + {std::nextafter(-1.5, -1.0), -1}, + {2147483647.5, 2147483648LL}, + {-2147483648.5, -2147483648LL}, + {4503599627370495.5, 4503599627370496LL}, + {4503599627370497.0, 4503599627370497LL}, + {-4503599627370497.0, -4503599627370497LL}, + {std::nextafter(9223372036854775808.0, 0.0), 9223372036854774784LL}, + {std::nextafter(-9223372036854775808.0, 0.0), -9223372036854774784LL}}; + for (const auto& item : cases) { + SCOPED_TRACE(item.first); + EXPECT_EQ(java_round(item.first), item.second); + const int64_t expected = std::max( + std::numeric_limits::min(), + std::min(std::numeric_limits::max(), + item.second)); + EXPECT_EQ(java_round(item.first), expected); + } +} + } // namespace From 1e8d230805d5823f1e0cd7a82af4e815510080e0 Mon Sep 17 00:00:00 2001 From: HUANG Xiangdong <1021782+jixuan1989@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:25:09 +0800 Subject: [PATCH 4/5] Borrow device IDs during record schema checks --- cpp/src/writer/tsfile_writer.cc | 4 ++-- cpp/src/writer/tsfile_writer.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/src/writer/tsfile_writer.cc b/cpp/src/writer/tsfile_writer.cc index a354bb41aa..8a960db577 100644 --- a/cpp/src/writer/tsfile_writer.cc +++ b/cpp/src/writer/tsfile_writer.cc @@ -510,7 +510,7 @@ std::shared_ptr TsFileWriter::get_table_schema( template int TsFileWriter::do_check_schema( - std::shared_ptr device_id, + const std::shared_ptr& device_id, MeasurementNamesGetter& measurement_names, SimpleVector& chunk_writers, SimpleVector& data_types) { @@ -583,7 +583,7 @@ int TsFileWriter::ensure_aligned_value_chunk_writer( template int TsFileWriter::do_check_schema_aligned( - std::shared_ptr device_id, + const std::shared_ptr& device_id, MeasurementNamesGetter& measurement_names, storage::TimeChunkWriter*& time_chunk_writer, common::SimpleVector& value_chunk_writers, diff --git a/cpp/src/writer/tsfile_writer.h b/cpp/src/writer/tsfile_writer.h index 0606a7a33e..b20d8d0adb 100644 --- a/cpp/src/writer/tsfile_writer.h +++ b/cpp/src/writer/tsfile_writer.h @@ -167,14 +167,14 @@ class TsFileWriter { template int do_check_schema( - std::shared_ptr device_id, + const std::shared_ptr& device_id, MeasurementNamesGetter& measurement_names, common::SimpleVector& chunk_writers, common::SimpleVector& data_types); template int do_check_schema_aligned( - std::shared_ptr device_id, + const std::shared_ptr& device_id, MeasurementNamesGetter& measurement_names, storage::TimeChunkWriter*& time_chunk_writer, common::SimpleVector& value_chunk_writers, From 75592b89d1523a96090e8bca1dcb4cd2180162e5 Mon Sep 17 00:00:00 2001 From: HUANG Xiangdong <1021782+jixuan1989@users.noreply.github.com> Date: Sun, 18 Oct 2026 03:48:35 +0800 Subject: [PATCH 5/5] Fix precision boundary test decoder lifetime under ASan --- cpp/test/encoding/encoding_props_test.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/test/encoding/encoding_props_test.cc b/cpp/test/encoding/encoding_props_test.cc index 3b059cdc46..c4b4fb3471 100644 --- a/cpp/test/encoding/encoding_props_test.cc +++ b/cpp/test/encoding/encoding_props_test.cc @@ -19,8 +19,8 @@ #include -#include "encoding/decoder_factory.h" #include "encoding/encoder_factory.h" +#include "encoding/ts2diff_decoder.h" namespace { using namespace common; @@ -211,9 +211,11 @@ TEST(EncodingProps, ZeroOnlyPagesEscapeReservedPrecisionMarkers) { EXPECT_EQ(stored_precision, static_cast(precision)); } - auto* decoder = - DecoderFactory::alloc_value_decoder(TS_2DIFF, type); - ASSERT_NE(decoder, nullptr); + FloatTS2DIFFDecoder float_decoder; + DoubleTS2DIFFDecoder double_decoder; + Decoder* decoder = + type == FLOAT ? static_cast(&float_decoder) + : static_cast(&double_decoder); for (int row = 0; row < rows; ++row) { double actual = -1; if (type == FLOAT) { @@ -228,7 +230,6 @@ TEST(EncodingProps, ZeroOnlyPagesEscapeReservedPrecisionMarkers) { EXPECT_FALSE(std::signbit(actual)); } EXPECT_FALSE(decoder->has_remaining(bytes)); - DecoderFactory::free(decoder); encoder->reset(); } }