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);