This is an automated email from the ASF dual-hosted git repository.

jt2594838 pushed a commit to branch rc/2.0.10
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rc/2.0.10 by this push:
     new 137bfbf810a Backport client fixes to rc/2.0.10: fix DATE NULL handling 
crash and add missing numeric getters for Column (#18095)
137bfbf810a is described below

commit 137bfbf810aa59dec6797bc3e634c75a79e10cf7
Author: Hongzhi Gao <[email protected]>
AuthorDate: Fri Jul 3 10:04:02 2026 +0800

    Backport client fixes to rc/2.0.10: fix DATE NULL handling crash and add 
missing numeric getters for Column (#18095)
    
    * Fix C++ client DATE 1000-01-01 query crash
    
    Stop treating EMPTY_DATE_INT as a null sentinel when decoding query 
results, since it encodes the valid date 1000-01-01 and caused C API reads to 
throw in parseDateExpressionToInt.
    
    * Fix C++ client reading FLOAT inference columns declared as DOUBLE (#17759)
    
    * 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.
    
    * Move column type coercion test into sessionIT.cpp
    
    Avoid a separate test source file; keep the same coverage in session_tests.
    
    * 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/include/Column.h     |  5 ++++
 iotdb-client/client-cpp/src/include/Common.h     |  2 +-
 iotdb-client/client-cpp/src/session/Column.cpp   | 20 ++++++++++++++++
 iotdb-client/client-cpp/src/session/Date.cpp     |  3 ---
 iotdb-client/client-cpp/src/session/SessionC.cpp |  4 +++-
 iotdb-client/client-cpp/test/cpp/sessionCIT.cpp  | 30 ++++++++++++++++++++++++
 iotdb-client/client-cpp/test/cpp/sessionIT.cpp   | 28 ++++++++++++++++++++++
 7 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/iotdb-client/client-cpp/src/include/Column.h 
b/iotdb-client/client-cpp/src/include/Column.h
index 8794e7b8d5e..c278c6ebe73 100644
--- a/iotdb-client/client-cpp/src/include/Column.h
+++ b/iotdb-client/client-cpp/src/include/Column.h
@@ -196,6 +196,9 @@ public:
   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<int32_t> getInts() const override;
 
   bool mayHaveNull() const override;
@@ -220,6 +223,7 @@ public:
   ColumnEncoding getEncoding() const override;
 
   float getFloat(int32_t position) const override;
+  double getDouble(int32_t position) const override;
   std::vector<float> getFloats() const override;
 
   bool mayHaveNull() const override;
@@ -244,6 +248,7 @@ public:
   ColumnEncoding getEncoding() const override;
 
   int64_t getLong(int32_t position) const override;
+  double getDouble(int32_t position) const override;
   std::vector<int64_t> getLongs() const override;
 
   bool mayHaveNull() const override;
diff --git a/iotdb-client/client-cpp/src/include/Common.h 
b/iotdb-client/client-cpp/src/include/Common.h
index 49dca0bca4b..f36b38206b8 100644
--- a/iotdb-client/client-cpp/src/include/Common.h
+++ b/iotdb-client/client-cpp/src/include/Common.h
@@ -185,7 +185,7 @@ public:
     case TSDataType::BLOB:
       return !stringV.is_initialized();
     case TSDataType::DATE:
-      return !dateV.is_initialized();
+      return !dateV.is_initialized() || dateV.value().is_not_a_date();
     default:
       return true;
     }
diff --git a/iotdb-client/client-cpp/src/session/Column.cpp 
b/iotdb-client/client-cpp/src/session/Column.cpp
index 203fcefd8d9..906a78968ed 100644
--- a/iotdb-client/client-cpp/src/session/Column.cpp
+++ b/iotdb-client/client-cpp/src/session/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<int32_t> 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<float> 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<int64_t> LongColumn::getLongs() const {
   return values_;
 }
diff --git a/iotdb-client/client-cpp/src/session/Date.cpp 
b/iotdb-client/client-cpp/src/session/Date.cpp
index 91095c250da..1e6f62edd1d 100644
--- a/iotdb-client/client-cpp/src/session/Date.cpp
+++ b/iotdb-client/client-cpp/src/session/Date.cpp
@@ -53,9 +53,6 @@ int32_t parseDateExpressionToInt(const IoTDBDate& date) {
 }
 
 IoTDBDate parseIntToDate(int32_t dateInt) {
-  if (dateInt == EMPTY_DATE_INT) {
-    return IoTDBDate::notADate();
-  }
   const int year = dateInt / 10000;
   const int month = (dateInt % 10000) / 100;
   const int day = dateInt % 100;
diff --git a/iotdb-client/client-cpp/src/session/SessionC.cpp 
b/iotdb-client/client-cpp/src/session/SessionC.cpp
index 7d86bd348f8..17948793e74 100644
--- a/iotdb-client/client-cpp/src/session/SessionC.cpp
+++ b/iotdb-client/client-cpp/src/session/SessionC.cpp
@@ -1487,8 +1487,10 @@ int32_t ts_row_record_get_date_int32(CRowRecord* record, 
int index) {
   if (index < 0 || index >= (int)record->cpp->fields.size())
     return 0;
   const Field& f = record->cpp->fields[index];
-  if (f.dataType != TSDataType::DATE || !f.dateV.is_initialized())
+  if (f.dataType != TSDataType::DATE || !f.dateV.is_initialized() ||
+      f.dateV.value().is_not_a_date()) {
     return 0;
+  }
   return parseDateExpressionToInt(f.dateV.value());
 }
 
diff --git a/iotdb-client/client-cpp/test/cpp/sessionCIT.cpp 
b/iotdb-client/client-cpp/test/cpp/sessionCIT.cpp
index b79dd1100e6..892a307082e 100644
--- a/iotdb-client/client-cpp/test/cpp/sessionCIT.cpp
+++ b/iotdb-client/client-cpp/test/cpp/sessionCIT.cpp
@@ -771,3 +771,33 @@ TEST_CASE("C API - RowRecord and delete data APIs", 
"[c_rowDelete]") {
   REQUIRE(ts_session_delete_timeseries(g_session, pblob) == TS_OK);
   REQUIRE(ts_session_delete_database(g_session, sg) == TS_OK);
 }
+
+TEST_CASE("C API - Query DATE 1000-01-01", "[c_dateMinYear]") {
+  CaseReporter cr("c_dateMinYear");
+
+  const char* path = "root.ctest.d1.s_date_min";
+  ensureTimeseries(g_session, path, TS_TYPE_DATE, TS_ENCODING_PLAIN, 
TS_COMPRESSION_SNAPPY);
+
+  const char* deviceId = "root.ctest.d1";
+  const char* measurements[] = {"s_date_min"};
+  TSDataType_C types[] = {TS_TYPE_DATE};
+  TSDate_C dateVal = {1000, 1, 1};
+  const void* vals[] = {&dateVal};
+  REQUIRE(ts_session_insert_record(g_session, deviceId, 1000LL, 1, 
measurements, types, vals) ==
+          TS_OK);
+
+  CSessionDataSet* dataSet = nullptr;
+  REQUIRE(ts_session_execute_query(g_session,
+                                   "select s_date_min from root.ctest.d1 where 
time=1000",
+                                   &dataSet) == TS_OK);
+  REQUIRE(dataSet != nullptr);
+  REQUIRE(ts_dataset_has_next(dataSet));
+  CRowRecord* record = ts_dataset_next(dataSet);
+  REQUIRE(record != nullptr);
+  REQUIRE_FALSE(ts_row_record_is_null(record, 0));
+  REQUIRE(ts_row_record_get_date_int32(record, 0) == 10000101);
+  ts_row_record_destroy(record);
+  ts_dataset_destroy(dataSet);
+
+  REQUIRE(ts_session_delete_timeseries(g_session, path) == TS_OK);
+}
diff --git a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp 
b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
index c0428f6e84a..59c7c944929 100644
--- a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
+++ b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
@@ -1037,3 +1037,31 @@ TEST_CASE("SessionPool getSession times out when 
exhausted", "[sessionPool]") {
   reused.release();
   pool->close();
 }
+
+TEST_CASE("Query DATE 1000-01-01", "[dateMinYear]") {
+  CaseReporter cr("dateMinYear");
+  const string timeseries = "root.test.d1.s_date_min";
+  if (session->checkTimeseriesExists(timeseries)) {
+    session->deleteTimeseries(timeseries);
+  }
+  session->createTimeseries(timeseries, TSDataType::DATE, TSEncoding::PLAIN,
+                            CompressionType::SNAPPY);
+
+  const string deviceId = "root.test.d1";
+  const vector<string> measurements = {"s_date_min"};
+  const vector<TSDataType::TSDataType> types = {TSDataType::DATE};
+  const IoTDBDate dateValue(1000, 1, 1);
+  vector<char*> values = {const_cast<char*>(reinterpret_cast<const 
char*>(&dateValue))};
+  session->insertRecord(deviceId, 1000LL, measurements, types, values);
+
+  unique_ptr<SessionDataSet> sessionDataSet =
+      session->executeQueryStatement("select s_date_min from root.test.d1 
where time=1000");
+  REQUIRE(sessionDataSet->hasNext());
+  auto record = sessionDataSet->next();
+  REQUIRE(record->fields.size() == 1);
+  REQUIRE_FALSE(record->fields[0].isNull());
+  REQUIRE(record->fields[0].dateV.value() == dateValue);
+  REQUIRE(record->fields[0].dateV.value().toIsoExtendedString() == 
"1000-01-01");
+
+  session->deleteTimeseries(timeseries);
+}

Reply via email to