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

JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new c2ead3be72f Fix Java client time column access throwing 
ArrayIndexOutOfBoundsException (#17407) (#17408)
c2ead3be72f is described below

commit c2ead3be72f8944ef6c8d1349087cdb8377d0160
Author: ZIHAN DAI <[email protected]>
AuthorDate: Thu Sep 10 17:55:38 2026 +1000

    Fix Java client time column access throwing ArrayIndexOutOfBoundsException 
(#17407) (#17408)
---
 .../apache/iotdb/jdbc/IoTDBJDBCResultSetTest.java  | 90 ++++++++++++++++++++++
 .../en/org/apache/iotdb/rpc/i18n/RpcMessages.java  | 10 +++
 .../zh/org/apache/iotdb/rpc/i18n/RpcMessages.java  |  5 ++
 .../java/org/apache/iotdb/rpc/IoTDBRpcDataSet.java | 19 +++++
 4 files changed, 124 insertions(+)

diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSetTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSetTest.java
index 3fb60b8760d..5805fb10330 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSetTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSetTest.java
@@ -43,6 +43,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Timestamp;
 import java.sql.Types;
@@ -260,6 +261,95 @@ public class IoTDBJDBCResultSetTest {
     verify(fetchResultsResp, times(0)).getStatus();
   }
 
+  /**
+   * The tree-model time pseudo-column is addressed by a negative TsBlock 
column index. getLong,
+   * getString and getObject handle it; the strictly-typed getters used to 
fall through to
+   * TsBlock.getColumn(-1) and escape as an unchecked 
ArrayIndexOutOfBoundsException instead of the
+   * SQLException a JDBC caller can handle. getDate delegates to getInt, so it 
is affected too.
+   */
+  @SuppressWarnings("resource")
+  @Test
+  public void testTimeColumnRejectedByStrictlyTypedGetters() throws Exception {
+    try (ResultSet resultSet = executeAndFetchTreeModelResultSet()) {
+      Assert.assertEquals(1, resultSet.findColumn("Time"));
+      Assert.assertTrue(resultSet.next());
+
+      // The getters that support the time column keep working.
+      Assert.assertEquals(2L, resultSet.getLong(1));
+      Assert.assertEquals("2", resultSet.getString(1));
+      Assert.assertEquals(new Timestamp(2), resultSet.getObject(1));
+      Assert.assertEquals(new Timestamp(2), resultSet.getTimestamp(1));
+
+      // The strictly-typed ones must report a SQLException, not an unchecked 
exception.
+      assertTimeColumnRejected(() -> resultSet.getBoolean(1));
+      assertTimeColumnRejected(() -> resultSet.getInt(1));
+      assertTimeColumnRejected(() -> resultSet.getFloat(1));
+      assertTimeColumnRejected(() -> resultSet.getDouble(1));
+      assertTimeColumnRejected(() -> resultSet.getDate(1));
+
+      // Reading by name goes through the same path.
+      assertTimeColumnRejected(() -> resultSet.getInt("Time"));
+    }
+  }
+
+  @FunctionalInterface
+  private interface ResultSetRead {
+    void run() throws SQLException;
+  }
+
+  private void assertTimeColumnRejected(ResultSetRead read) {
+    try {
+      read.run();
+      Assert.fail("reading the time column with a strictly-typed getter should 
have thrown");
+    } catch (SQLException e) {
+      // expected
+    } catch (RuntimeException e) {
+      Assert.fail(
+          "expected a SQLException but the time column leaked an unchecked "
+              + e.getClass().getName()
+              + ": "
+              + e.getMessage());
+    }
+  }
+
+  /** Same tree-model fixture as testQuery: column 1 is Time, columns 2..5 are 
measurements. */
+  private ResultSet executeAndFetchTreeModelResultSet() throws Exception {
+    List<String> columns = new ArrayList<>();
+    columns.add("root.vehicle.d0.s2");
+    columns.add("root.vehicle.d0.s1");
+    columns.add("root.vehicle.d0.s0");
+    columns.add("root.vehicle.d0.s2");
+
+    List<String> dataTypeList = new ArrayList<>();
+    dataTypeList.add("FLOAT");
+    dataTypeList.add("INT64");
+    dataTypeList.add("INT32");
+    dataTypeList.add("FLOAT");
+
+    when(execResp.isSetColumns()).thenReturn(true);
+    when(execResp.getColumns()).thenReturn(columns);
+    when(execResp.isSetDataTypeList()).thenReturn(true);
+    when(execResp.getDataTypeList()).thenReturn(dataTypeList);
+    when(execResp.isSetOperationType()).thenReturn(true);
+    when(execResp.getOperationType()).thenReturn("QUERY");
+    when(execResp.isSetQueryId()).thenReturn(true);
+    when(execResp.getQueryId()).thenReturn(queryId);
+    when(execResp.isSetTableModel()).thenReturn(false);
+    when(execResp.isIgnoreTimeStamp()).thenReturn(false);
+
+    List<Integer> columnIndex2TsBlockColumnIndexList = new 
ArrayList<>(columns.size());
+    columnIndex2TsBlockColumnIndexList.add(0);
+    columnIndex2TsBlockColumnIndexList.add(1);
+    columnIndex2TsBlockColumnIndexList.add(2);
+    columnIndex2TsBlockColumnIndexList.add(0);
+    when(execResp.getColumnIndex2TsBlockColumnIndexList())
+        .thenReturn(columnIndex2TsBlockColumnIndexList);
+
+    Assert.assertTrue(statement.execute("select * from root.vehicle.d0"));
+    fetchResultsResp.hasResultSet = true;
+    return statement.getResultSet();
+  }
+
   private void constructObjectList(List<Object> standardObject) {
     Object[][] input = {
       {
diff --git 
a/iotdb-client/service-rpc/src/main/i18n/en/org/apache/iotdb/rpc/i18n/RpcMessages.java
 
b/iotdb-client/service-rpc/src/main/i18n/en/org/apache/iotdb/rpc/i18n/RpcMessages.java
index d8612aa66a7..b9d76aa4fd9 100644
--- 
a/iotdb-client/service-rpc/src/main/i18n/en/org/apache/iotdb/rpc/i18n/RpcMessages.java
+++ 
b/iotdb-client/service-rpc/src/main/i18n/en/org/apache/iotdb/rpc/i18n/RpcMessages.java
@@ -68,6 +68,16 @@ public final class RpcMessages {
       "column index %d out of range %d";
   public static final String UNKNOWN_COLUMN_NAME = "Unknown column name: ";
   public static final String NO_RECORD_REMAINS = "No record remains";
+  public static final String CANNOT_READ_BOOLEAN_FROM_TIME_COLUMN =
+      "Cannot read boolean from time column";
+  public static final String CANNOT_READ_DOUBLE_FROM_TIME_COLUMN =
+      "Cannot read double from time column";
+  public static final String CANNOT_READ_FLOAT_FROM_TIME_COLUMN =
+      "Cannot read float from time column";
+  public static final String CANNOT_READ_INT32_FROM_TIME_COLUMN =
+      "Cannot read int32 from time column";
+  public static final String CANNOT_READ_BINARY_FROM_TIME_COLUMN =
+      "Cannot read binary from time column";
   public static final String CANNOT_CLOSE_DATASET =
       "Cannot close dataset, because of network connection: {} ";
 
diff --git 
a/iotdb-client/service-rpc/src/main/i18n/zh/org/apache/iotdb/rpc/i18n/RpcMessages.java
 
b/iotdb-client/service-rpc/src/main/i18n/zh/org/apache/iotdb/rpc/i18n/RpcMessages.java
index c340b54c764..5ddfe9fc0b2 100644
--- 
a/iotdb-client/service-rpc/src/main/i18n/zh/org/apache/iotdb/rpc/i18n/RpcMessages.java
+++ 
b/iotdb-client/service-rpc/src/main/i18n/zh/org/apache/iotdb/rpc/i18n/RpcMessages.java
@@ -58,6 +58,11 @@ public final class RpcMessages {
   public static final String COLUMN_INDEX_OUT_OF_RANGE = "列索引 %d 超出范围 %d";
   public static final String UNKNOWN_COLUMN_NAME = "未知列名:";
   public static final String NO_RECORD_REMAINS = "没有剩余记录";
+  public static final String CANNOT_READ_BOOLEAN_FROM_TIME_COLUMN = "无法从时间列读取 
boolean 值";
+  public static final String CANNOT_READ_DOUBLE_FROM_TIME_COLUMN = "无法从时间列读取 
double 值";
+  public static final String CANNOT_READ_FLOAT_FROM_TIME_COLUMN = "无法从时间列读取 
float 值";
+  public static final String CANNOT_READ_INT32_FROM_TIME_COLUMN = "无法从时间列读取 
int32 值";
+  public static final String CANNOT_READ_BINARY_FROM_TIME_COLUMN = "无法从时间列读取 
binary 值";
   public static final String CANNOT_CLOSE_DATASET = "无法关闭数据集,网络连接异常:{} ";
 
   // RpcUtils
diff --git 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/IoTDBRpcDataSet.java
 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/IoTDBRpcDataSet.java
index 7619be16734..56200399b4e 100644
--- 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/IoTDBRpcDataSet.java
+++ 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/IoTDBRpcDataSet.java
@@ -330,9 +330,16 @@ public class IoTDBRpcDataSet {
     return 
getBooleanByTsBlockColumnIndex(getTsBlockColumnIndexForColumnName(columnName));
   }
 
+  // A negative tsBlockColumnIndex denotes the tree-model time pseudo-column. 
Only getLong,
+  // getString and getObject can serve it; the strictly-typed getters reject 
it here so that
+  // callers see a StatementExecutionException instead of an 
ArrayIndexOutOfBoundsException
+  // escaping from TsBlock.getColumn(-1).
   private boolean getBooleanByTsBlockColumnIndex(int tsBlockColumnIndex)
       throws StatementExecutionException {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+      throw new 
StatementExecutionException(RpcMessages.CANNOT_READ_BOOLEAN_FROM_TIME_COLUMN);
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex)) {
       lastReadWasNull = false;
       return curTsBlock.getColumn(tsBlockColumnIndex).getBoolean(tsBlockIndex);
@@ -353,6 +360,9 @@ public class IoTDBRpcDataSet {
   private double getDoubleByTsBlockColumnIndex(int tsBlockColumnIndex)
       throws StatementExecutionException {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+      throw new 
StatementExecutionException(RpcMessages.CANNOT_READ_DOUBLE_FROM_TIME_COLUMN);
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex)) {
       lastReadWasNull = false;
       return curTsBlock.getColumn(tsBlockColumnIndex).getDouble(tsBlockIndex);
@@ -373,6 +383,9 @@ public class IoTDBRpcDataSet {
   private float getFloatByTsBlockColumnIndex(int tsBlockColumnIndex)
       throws StatementExecutionException {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+      throw new 
StatementExecutionException(RpcMessages.CANNOT_READ_FLOAT_FROM_TIME_COLUMN);
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex)) {
       lastReadWasNull = false;
       return curTsBlock.getColumn(tsBlockColumnIndex).getFloat(tsBlockIndex);
@@ -393,6 +406,9 @@ public class IoTDBRpcDataSet {
   private int getIntByTsBlockColumnIndex(int tsBlockColumnIndex)
       throws StatementExecutionException {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+      throw new 
StatementExecutionException(RpcMessages.CANNOT_READ_INT32_FROM_TIME_COLUMN);
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex)) {
       lastReadWasNull = false;
       TSDataType type = curTsBlock.getColumn(tsBlockColumnIndex).getDataType();
@@ -451,6 +467,9 @@ public class IoTDBRpcDataSet {
   private Binary getBinaryTsBlockColumnIndex(int tsBlockColumnIndex)
       throws StatementExecutionException {
     checkRecord();
+    if (tsBlockColumnIndex < 0) {
+      throw new 
StatementExecutionException(RpcMessages.CANNOT_READ_BINARY_FROM_TIME_COLUMN);
+    }
     if (!isNull(tsBlockColumnIndex, tsBlockIndex)) {
       lastReadWasNull = false;
       return curTsBlock.getColumn(tsBlockColumnIndex).getBinary(tsBlockIndex);

Reply via email to