From 3838f216ae32ca343ee0b4392044f055bd4b2a88 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Mon, 25 May 2026 14:59:58 +0800 Subject: [PATCH 1/3] Fix C++ client reading FLOAT inference columns declared as DOUBLE CALL INFERENCE returns FLOAT data in TsBlock while the result schema declares DOUBLE. Coerce by actual column type in getDouble/getFloat to avoid Unsupported operation: getDouble when iterating RowRecord. --- .../client-cpp/src/main/IoTDBRpcDataSet.cpp | 14 ++++++-- .../client-cpp/src/test/CMakeLists.txt | 2 +- .../src/test/cpp/columnTypeCoercionTest.cpp | 35 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp diff --git a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp index 8b02a35607950..14928644388dd 100644 --- a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp +++ b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp @@ -280,7 +280,12 @@ boost::optional IoTDBRpcDataSet::getDoubleByTsBlockColumnIndex(int32_t t } if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) { lastReadWasNull_ = false; - return curTsBlock_->getColumn(tsBlockColumnIndex)->getDouble(tsBlockIndex_); + auto column = curTsBlock_->getColumn(tsBlockColumnIndex); + TSDataType::TSDataType dataType = column->getDataType(); + if (dataType == TSDataType::FLOAT) { + return static_cast(column->getFloat(tsBlockIndex_)); + } + return column->getDouble(tsBlockIndex_); } else { lastReadWasNull_ = true; return boost::none; @@ -304,7 +309,12 @@ boost::optional IoTDBRpcDataSet::getFloatByTsBlockColumnIndex(int32_t tsB } if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) { lastReadWasNull_ = false; - return curTsBlock_->getColumn(tsBlockColumnIndex)->getFloat(tsBlockIndex_); + auto column = curTsBlock_->getColumn(tsBlockColumnIndex); + TSDataType::TSDataType dataType = column->getDataType(); + if (dataType == TSDataType::DOUBLE) { + return static_cast(column->getDouble(tsBlockIndex_)); + } + return column->getFloat(tsBlockIndex_); } else { lastReadWasNull_ = true; return boost::none; diff --git a/iotdb-client/client-cpp/src/test/CMakeLists.txt b/iotdb-client/client-cpp/src/test/CMakeLists.txt index 5a234b4709408..941aa8712d26d 100644 --- a/iotdb-client/client-cpp/src/test/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/test/CMakeLists.txt @@ -77,7 +77,7 @@ ENDIF() SET(TARGET_NAME_C session_c_tests) SET(TARGET_NAME_C_RELATIONAL session_c_relational_tests) -ADD_EXECUTABLE(${TARGET_NAME} main.cpp cpp/sessionIT.cpp) +ADD_EXECUTABLE(${TARGET_NAME} main.cpp cpp/sessionIT.cpp cpp/columnTypeCoercionTest.cpp) ADD_EXECUTABLE(${TARGET_NAME_RELATIONAL} main_Relational.cpp cpp/sessionRelationalIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_C} main_c.cpp cpp/sessionCIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_C_RELATIONAL} main_c_Relational.cpp cpp/sessionCRelationalIT.cpp) diff --git a/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp b/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp new file mode 100644 index 0000000000000..deb96128dd2fc --- /dev/null +++ b/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp @@ -0,0 +1,35 @@ +/** + * 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 "catch.hpp" +#include "Column.h" + +// Inference output may be FLOAT in TsBlock while schema declares DOUBLE. +TEST_CASE("Read float column as double for schema mismatch", "[column][inference]") { + std::vector valueIsNull(1, false); + std::vector values = {120.00000762939453f}; + auto floatColumn = std::make_shared(0, 1, valueIsNull, values); + auto rleColumn = std::make_shared(floatColumn, 20); + + REQUIRE(rleColumn->getDataType() == TSDataType::FLOAT); + REQUIRE_THROWS_AS(rleColumn->getDouble(0), IoTDBException); + + double asDouble = static_cast(rleColumn->getFloat(0)); + REQUIRE(asDouble == Approx(120.0).margin(0.01)); +} From ac9433239e8778402cb996f122d76fbdf3f89e04 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Mon, 25 May 2026 15:04:39 +0800 Subject: [PATCH 2/3] Move column type coercion test into sessionIT.cpp Avoid a separate test source file; keep the same coverage in session_tests. --- .../client-cpp/src/test/CMakeLists.txt | 2 +- .../src/test/cpp/columnTypeCoercionTest.cpp | 35 ------------------- .../client-cpp/src/test/cpp/sessionIT.cpp | 14 ++++++++ 3 files changed, 15 insertions(+), 36 deletions(-) delete mode 100644 iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp diff --git a/iotdb-client/client-cpp/src/test/CMakeLists.txt b/iotdb-client/client-cpp/src/test/CMakeLists.txt index 941aa8712d26d..5a234b4709408 100644 --- a/iotdb-client/client-cpp/src/test/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/test/CMakeLists.txt @@ -77,7 +77,7 @@ ENDIF() SET(TARGET_NAME_C session_c_tests) SET(TARGET_NAME_C_RELATIONAL session_c_relational_tests) -ADD_EXECUTABLE(${TARGET_NAME} main.cpp cpp/sessionIT.cpp cpp/columnTypeCoercionTest.cpp) +ADD_EXECUTABLE(${TARGET_NAME} main.cpp cpp/sessionIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_RELATIONAL} main_Relational.cpp cpp/sessionRelationalIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_C} main_c.cpp cpp/sessionCIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_C_RELATIONAL} main_c_Relational.cpp cpp/sessionCRelationalIT.cpp) diff --git a/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp b/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp deleted file mode 100644 index deb96128dd2fc..0000000000000 --- a/iotdb-client/client-cpp/src/test/cpp/columnTypeCoercionTest.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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 "catch.hpp" -#include "Column.h" - -// Inference output may be FLOAT in TsBlock while schema declares DOUBLE. -TEST_CASE("Read float column as double for schema mismatch", "[column][inference]") { - std::vector valueIsNull(1, false); - std::vector values = {120.00000762939453f}; - auto floatColumn = std::make_shared(0, 1, valueIsNull, values); - auto rleColumn = std::make_shared(floatColumn, 20); - - REQUIRE(rleColumn->getDataType() == TSDataType::FLOAT); - REQUIRE_THROWS_AS(rleColumn->getDouble(0), IoTDBException); - - double asDouble = static_cast(rleColumn->getFloat(0)); - REQUIRE(asDouble == Approx(120.0).margin(0.01)); -} diff --git a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp index 1f9abb153efd9..e5428126d40b4 100644 --- a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp +++ b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp @@ -18,6 +18,7 @@ */ #include "catch.hpp" +#include "Column.h" #include "Session.h" #include "SessionBuilder.h" #include "TsBlock.h" @@ -878,4 +879,17 @@ TEST_CASE("TsBlock deserialize rejects truncated malicious payload", "[TsBlockDe std::string data(18, '\0'); data[3] = '\x10'; REQUIRE_THROWS_AS(TsBlock::deserialize(data), IoTDBException); +} + +TEST_CASE("Read float column as double for schema mismatch", "[column][inference]") { + std::vector valueIsNull(1, false); + std::vector values = {120.00000762939453f}; + auto floatColumn = std::make_shared(0, 1, valueIsNull, values); + auto rleColumn = std::make_shared(floatColumn, 20); + + REQUIRE(rleColumn->getDataType() == TSDataType::FLOAT); + REQUIRE_THROWS_AS(rleColumn->getDouble(0), IoTDBException); + + double asDouble = static_cast(rleColumn->getFloat(0)); + REQUIRE(asDouble == Approx(120.0).margin(0.01)); } \ No newline at end of file From 8e066d40669274f6663e4aa8cdc5a3a719c81eeb Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Mon, 25 May 2026 15:33:57 +0800 Subject: [PATCH 3/3] Implement numeric widening getters on C++ Column classes Align with Java TsFile: IntColumn/FloatColumn/LongColumn support cross-type getters (e.g. FloatColumn::getDouble). IoTDBRpcDataSet delegates directly to column getters like the Java client. Fixes CALL INFERENCE crash when schema declares DOUBLE but TsBlock stores FLOAT (including RLE-encoded columns). --- iotdb-client/client-cpp/src/main/Column.cpp | 20 +++++++++++++++++++ iotdb-client/client-cpp/src/main/Column.h | 5 +++++ .../client-cpp/src/main/IoTDBRpcDataSet.cpp | 14 ++----------- .../client-cpp/src/test/cpp/sessionIT.cpp | 20 ++++++++++++------- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/iotdb-client/client-cpp/src/main/Column.cpp b/iotdb-client/client-cpp/src/main/Column.cpp index 203fcefd8d9a5..906a78968ed2c 100644 --- a/iotdb-client/client-cpp/src/main/Column.cpp +++ b/iotdb-client/client-cpp/src/main/Column.cpp @@ -152,6 +152,18 @@ int32_t IntColumn::getInt(int32_t position) const { return values_[position + arrayOffset_]; } +int64_t IntColumn::getLong(int32_t position) const { + return values_[position + arrayOffset_]; +} + +float IntColumn::getFloat(int32_t position) const { + return values_[position + arrayOffset_]; +} + +double IntColumn::getDouble(int32_t position) const { + return values_[position + arrayOffset_]; +} + std::vector IntColumn::getInts() const { return values_; } @@ -204,6 +216,10 @@ float FloatColumn::getFloat(int32_t position) const { return values_[position + arrayOffset_]; } +double FloatColumn::getDouble(int32_t position) const { + return values_[position + arrayOffset_]; +} + std::vector FloatColumn::getFloats() const { return values_; } @@ -256,6 +272,10 @@ int64_t LongColumn::getLong(int32_t position) const { return values_[position + arrayOffset_]; } +double LongColumn::getDouble(int32_t position) const { + return values_[position + arrayOffset_]; +} + std::vector LongColumn::getLongs() const { return values_; } diff --git a/iotdb-client/client-cpp/src/main/Column.h b/iotdb-client/client-cpp/src/main/Column.h index 8794e7b8d5e34..c278c6ebe7385 100644 --- a/iotdb-client/client-cpp/src/main/Column.h +++ b/iotdb-client/client-cpp/src/main/Column.h @@ -196,6 +196,9 @@ class IntColumn : public Column { ColumnEncoding getEncoding() const override; int32_t getInt(int32_t position) const override; + int64_t getLong(int32_t position) const override; + float getFloat(int32_t position) const override; + double getDouble(int32_t position) const override; std::vector getInts() const override; bool mayHaveNull() const override; @@ -220,6 +223,7 @@ class FloatColumn : public Column { ColumnEncoding getEncoding() const override; float getFloat(int32_t position) const override; + double getDouble(int32_t position) const override; std::vector getFloats() const override; bool mayHaveNull() const override; @@ -244,6 +248,7 @@ class LongColumn : public Column { ColumnEncoding getEncoding() const override; int64_t getLong(int32_t position) const override; + double getDouble(int32_t position) const override; std::vector getLongs() const override; bool mayHaveNull() const override; diff --git a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp index 14928644388dd..8b02a35607950 100644 --- a/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp +++ b/iotdb-client/client-cpp/src/main/IoTDBRpcDataSet.cpp @@ -280,12 +280,7 @@ boost::optional IoTDBRpcDataSet::getDoubleByTsBlockColumnIndex(int32_t t } if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) { lastReadWasNull_ = false; - auto column = curTsBlock_->getColumn(tsBlockColumnIndex); - TSDataType::TSDataType dataType = column->getDataType(); - if (dataType == TSDataType::FLOAT) { - return static_cast(column->getFloat(tsBlockIndex_)); - } - return column->getDouble(tsBlockIndex_); + return curTsBlock_->getColumn(tsBlockColumnIndex)->getDouble(tsBlockIndex_); } else { lastReadWasNull_ = true; return boost::none; @@ -309,12 +304,7 @@ boost::optional IoTDBRpcDataSet::getFloatByTsBlockColumnIndex(int32_t tsB } if (!isNull(tsBlockColumnIndex, tsBlockIndex_)) { lastReadWasNull_ = false; - auto column = curTsBlock_->getColumn(tsBlockColumnIndex); - TSDataType::TSDataType dataType = column->getDataType(); - if (dataType == TSDataType::DOUBLE) { - return static_cast(column->getDouble(tsBlockIndex_)); - } - return column->getFloat(tsBlockIndex_); + return curTsBlock_->getColumn(tsBlockColumnIndex)->getFloat(tsBlockIndex_); } else { lastReadWasNull_ = true; return boost::none; diff --git a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp index e5428126d40b4..0bf30bbbf1be9 100644 --- a/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp +++ b/iotdb-client/client-cpp/src/test/cpp/sessionIT.cpp @@ -881,15 +881,21 @@ TEST_CASE("TsBlock deserialize rejects truncated malicious payload", "[TsBlockDe REQUIRE_THROWS_AS(TsBlock::deserialize(data), IoTDBException); } -TEST_CASE("Read float column as double for schema mismatch", "[column][inference]") { +TEST_CASE("Numeric column widening getters align with Java TsFile", "[column]") { std::vector valueIsNull(1, false); - std::vector values = {120.00000762939453f}; - auto floatColumn = std::make_shared(0, 1, valueIsNull, values); + + std::vector floatValues = {120.00000762939453f}; + auto floatColumn = std::make_shared(0, 1, valueIsNull, floatValues); auto rleColumn = std::make_shared(floatColumn, 20); + REQUIRE(floatColumn->getDouble(0) == Approx(120.0).margin(0.01)); + REQUIRE(rleColumn->getDouble(0) == Approx(120.0).margin(0.01)); - REQUIRE(rleColumn->getDataType() == TSDataType::FLOAT); - REQUIRE_THROWS_AS(rleColumn->getDouble(0), IoTDBException); + std::vector intValues = {42}; + auto intColumn = std::make_shared(0, 1, valueIsNull, intValues); + REQUIRE(intColumn->getLong(0) == 42); + REQUIRE(intColumn->getDouble(0) == Approx(42.0)); - double asDouble = static_cast(rleColumn->getFloat(0)); - REQUIRE(asDouble == Approx(120.0).margin(0.01)); + std::vector longValues = {1000}; + auto longColumn = std::make_shared(0, 1, valueIsNull, longValues); + REQUIRE(longColumn->getDouble(0) == Approx(1000.0)); } \ No newline at end of file