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

Caideyipi pushed a commit to branch codex/jdbc-driver-info
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/codex/jdbc-driver-info by this 
push:
     new bc18c420c55 Fix JDBC edge case handling
bc18c420c55 is described below

commit bc18c420c55f696a95c41e99a28c1ad06721bcd2
Author: Caideyipi <[email protected]>
AuthorDate: Tue Jun 9 11:30:17 2026 +0800

    Fix JDBC edge case handling
---
 .../org/apache/iotdb/jdbc/IoTDBConnection.java     | 23 +++++-
 .../org/apache/iotdb/jdbc/IoTDBDataSource.java     | 11 +++
 .../apache/iotdb/jdbc/IoTDBDataSourceFactory.java  | 44 ++++++++++-
 .../apache/iotdb/jdbc/IoTDBPreparedStatement.java  | 85 ++++++++++++++++++----
 .../org/apache/iotdb/jdbc/IoTDBSQLException.java   |  4 +
 .../iotdb/jdbc/IoTDBTablePreparedStatement.java    | 57 ++++++++++++---
 .../src/main/java/org/apache/iotdb/jdbc/Utils.java | 15 ++++
 .../org/apache/iotdb/jdbc/IoTDBConnectionTest.java | 40 ++++++++++
 .../iotdb/jdbc/IoTDBDataSourceFactoryTest.java     | 38 ++++++++++
 .../org/apache/iotdb/jdbc/IoTDBDriverTest.java     |  5 +-
 .../iotdb/jdbc/IoTDBPreparedStatementTest.java     | 56 ++++++++++++++
 .../jdbc/IoTDBTablePreparedStatementTest.java      | 84 +++++++++++++++++++++
 .../test/java/org/apache/iotdb/jdbc/UtilsTest.java | 30 ++++++++
 13 files changed, 463 insertions(+), 29 deletions(-)

diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnection.java 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnection.java
index ee94386fe54..2a9cd3816df 100644
--- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnection.java
+++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnection.java
@@ -60,6 +60,7 @@ import java.sql.SQLXML;
 import java.sql.Savepoint;
 import java.sql.Statement;
 import java.sql.Struct;
+import java.time.DateTimeException;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -269,6 +270,9 @@ public class IoTDBConnection implements Connection {
   @Override
   public void setCatalog(String arg0) throws SQLException {
     if (getSqlDialect().equals(Constant.TABLE_DIALECT)) {
+      if (arg0 == null) {
+        throw new SQLException("catalog cannot be null");
+      }
       if (APACHE_IOTDB.equals(arg0)) {
         return;
       }
@@ -343,6 +347,9 @@ public class IoTDBConnection implements Connection {
   public void setSchema(String arg0) throws SQLException {
     // changeDefaultDatabase(arg0);
     if (getSqlDialect().equals(Constant.TABLE_DIALECT)) {
+      if (arg0 == null) {
+        throw new SQLException("schema cannot be null");
+      }
       for (String str : 
IoTDBRelationalDatabaseMetadata.allIotdbTableSQLKeywords) {
         if (arg0.equalsIgnoreCase(str)) {
           arg0 = "\"" + arg0 + "\"";
@@ -486,7 +493,7 @@ public class IoTDBConnection implements Connection {
 
   @Override
   public void setClientInfo(String name, String value) throws 
SQLClientInfoException {
-    if (name.equalsIgnoreCase("time_zone")) {
+    if ("time_zone".equalsIgnoreCase(name)) {
       try {
         setTimeZone(value);
       } catch (TException | IoTDBSQLException e) {
@@ -669,6 +676,7 @@ public class IoTDBConnection implements Connection {
   }
 
   public void setTimeZone(String timeZone) throws TException, 
IoTDBSQLException {
+    ZoneId newZoneId = parseTimeZone(timeZone);
     TSSetTimeZoneReq req = new TSSetTimeZoneReq(sessionId, timeZone);
     TSStatus resp = getClient().setTimeZone(req);
     try {
@@ -676,7 +684,18 @@ public class IoTDBConnection implements Connection {
     } catch (StatementExecutionException e) {
       throw new IoTDBSQLException(e.getMessage(), resp);
     }
-    this.zoneId = ZoneId.of(timeZone);
+    this.zoneId = newZoneId;
+  }
+
+  private static ZoneId parseTimeZone(String timeZone) throws 
IoTDBSQLException {
+    if (timeZone == null) {
+      throw new IoTDBSQLException("Invalid time_zone: null");
+    }
+    try {
+      return ZoneId.of(timeZone);
+    } catch (DateTimeException e) {
+      throw new IoTDBSQLException("Invalid time_zone: " + timeZone, e);
+    }
   }
 
   public ServerProperties getServerProperties() throws TException {
diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSource.java 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSource.java
index 689b08a45dd..23ccf32fcd0 100644
--- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSource.java
+++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSource.java
@@ -84,6 +84,9 @@ public class IoTDBDataSource implements DataSource {
   }
 
   public void setPort(Integer port) {
+    if (port != null && (port < 0 || port > 65535)) {
+      throw new IllegalArgumentException("port must be between 0 and 65535");
+    }
     this.port = port == null || port == 0 ? Config.IOTDB_DEFAULT_PORT : port;
   }
 
@@ -151,6 +154,14 @@ public class IoTDBDataSource implements DataSource {
     this.url = url;
   }
 
+  String getConnectionProperty(String key) {
+    return properties.getProperty(key);
+  }
+
+  void setConnectionProperty(String key, String value) {
+    setProperty(key, value);
+  }
+
   @Override
   public Connection getConnection() throws SQLException {
     return createConnection((Properties) properties.clone());
diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactory.java
 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactory.java
index 98d6bfb777c..bf2ec57cc9e 100644
--- 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactory.java
+++ 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactory.java
@@ -99,11 +99,16 @@ public class IoTDBDataSourceFactory implements 
DataSourceFactory {
     }
 
     removeUnsupportedPoolProperties(properties);
+    applyConnectionProperties(ds, properties);
 
     logger.info(JdbcMessages.REMAINING_PROPERTIES, properties.size());
 
     if (!properties.isEmpty()) {
-      BeanConfig.configure(ds, properties);
+      try {
+        BeanConfig.configure(ds, properties);
+      } catch (IllegalArgumentException e) {
+        throw new SQLException("Invalid JDBC DataSource property", e);
+      }
     }
   }
 
@@ -128,6 +133,43 @@ public class IoTDBDataSourceFactory implements 
DataSourceFactory {
     return value == null ? null : value.toString();
   }
 
+  private static void applyConnectionProperties(IoTDBDataSource ds, Properties 
properties) {
+    applyCredentialProperty(ds, properties, Config.AUTH_USER);
+    applyCredentialProperty(ds, properties, Config.AUTH_PASSWORD);
+    applyConnectionProperty(ds, properties, Config.DEFAULT_BUFFER_CAPACITY);
+    applyConnectionProperty(ds, properties, Config.THRIFT_FRAME_MAX_SIZE);
+    applyConnectionProperty(ds, properties, Config.VERSION);
+    applyConnectionProperty(ds, properties, Config.NETWORK_TIMEOUT);
+    applyConnectionProperty(ds, properties, Config.TIME_ZONE);
+    applyConnectionProperty(ds, properties, Config.CHARSET);
+    applyConnectionProperty(ds, properties, Config.USE_SSL);
+    applyConnectionProperty(ds, properties, Config.TRUST_STORE);
+    applyConnectionProperty(ds, properties, Config.TRUST_STORE_PWD);
+    applyConnectionProperty(ds, properties, Utils.RPC_COMPRESS);
+    applyConnectionProperty(ds, properties, Config.SQL_DIALECT);
+  }
+
+  private static void applyCredentialProperty(
+      IoTDBDataSource ds, Properties properties, String key) {
+    String value = removeStringProperty(properties, key);
+    if (value == null) {
+      return;
+    }
+    if (Config.AUTH_USER.equals(key)) {
+      ds.setUser(value);
+    } else {
+      ds.setPassword(value);
+    }
+  }
+
+  private static void applyConnectionProperty(
+      IoTDBDataSource ds, Properties properties, String key) {
+    String value = removeStringProperty(properties, key);
+    if (value != null) {
+      ds.setConnectionProperty(key, value);
+    }
+  }
+
   private static Integer removeIntegerProperty(Properties properties, String 
key)
       throws SQLException {
     Object value = properties.remove(key);
diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
index 647b74a9e1a..384a85e228e 100644
--- 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
+++ 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
@@ -134,28 +134,32 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
       }
 
       @Override
-      public int isNullable(int param) {
+      public int isNullable(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return ParameterMetaData.parameterNullableUnknown;
       }
 
       @Override
-      public boolean isSigned(int param) {
+      public boolean isSigned(int param) throws SQLException {
+        String value = getParameterMetadataValue(param);
         try {
-          return Integer.parseInt(parameters.get(param)) < 0;
+          return Integer.parseInt(value) < 0;
         } catch (Exception e) {
           return false;
         }
       }
 
       @Override
-      public int getPrecision(int param) {
-        return parameters.get(param).length();
+      public int getPrecision(int param) throws SQLException {
+        String value = getParameterMetadataValue(param);
+        return value == null ? 0 : value.length();
       }
 
       @Override
-      public int getScale(int param) {
+      public int getScale(int param) throws SQLException {
+        String value = getParameterMetadataValue(param);
         try {
-          double d = Double.parseDouble(parameters.get(param));
+          double d = Double.parseDouble(value);
           if (d >= 1) { // we only need the fraction digits
             d = d - (long) d;
           }
@@ -175,23 +179,27 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
       }
 
       @Override
-      public int getParameterType(int param) {
-        return 0;
+      public int getParameterType(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
+        return Types.NULL;
       }
 
       @Override
-      public String getParameterTypeName(int param) {
+      public String getParameterTypeName(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return null;
       }
 
       @Override
-      public String getParameterClassName(int param) {
+      public String getParameterClassName(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return null;
       }
 
       @Override
-      public int getParameterMode(int param) {
-        return 0;
+      public int getParameterMode(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
+        return ParameterMetaData.parameterModeUnknown;
       }
 
       @Override
@@ -206,6 +214,17 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
     };
   }
 
+  private void checkParameterMetadataIndex(int param) throws SQLException {
+    if (param < 1 || !parameters.containsKey(param)) {
+      throw new SQLException("Parameter index is not set: " + param);
+    }
+  }
+
+  private String getParameterMetadataValue(int param) throws SQLException {
+    checkParameterMetadataIndex(param);
+    return parameters.get(param);
+  }
+
   @Override
   public void setArray(int parameterIndex, Array x) throws SQLException {
     throw new SQLException(Constant.PARAMETER_SUPPORTED);
@@ -238,6 +257,13 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setBinaryStream(int parameterIndex, InputStream x, int length) 
throws SQLException {
+    if (length < 0) {
+      throw new SQLException("length must be >= 0");
+    }
+    if (x == null) {
+      setNull(parameterIndex, Types.BINARY);
+      return;
+    }
     byte[] bytes = null;
     try {
       bytes = ReadWriteIOUtils.readBytes(x, length);
@@ -284,6 +310,10 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.BINARY);
+      return;
+    }
     Binary binary = new Binary(x);
     this.parameters.put(parameterIndex, 
binary.getStringValue(TSFileConfig.STRING_CHARSET));
   }
@@ -322,6 +352,10 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setDate(int parameterIndex, Date x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.DATE);
+      return;
+    }
     DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     this.parameters.put(parameterIndex, "'" + dateFormat.format(x) + "'");
   }
@@ -394,7 +428,9 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setObject(int parameterIndex, Object x) throws SQLException {
-    if (x instanceof String) {
+    if (x == null) {
+      setNull(parameterIndex, Types.NULL);
+    } else if (x instanceof String) {
       setString(parameterIndex, (String) x);
     } else if (x instanceof Integer) {
       setInt(parameterIndex, (Integer) x);
@@ -924,6 +960,10 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setTime(int parameterIndex, Time x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIME);
+      return;
+    }
     try {
       long time = x.getTime();
       String timeprecision = client.getProperties().getTimestampPrecision();
@@ -948,6 +988,10 @@ public class IoTDBPreparedStatement extends IoTDBStatement 
implements PreparedSt
 
   @Override
   public void setTime(int parameterIndex, Time x, Calendar cal) throws 
SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIME);
+      return;
+    }
     try {
       ZonedDateTime zonedDateTime = null;
       long time = x.getTime();
@@ -980,7 +1024,11 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
   }
 
   @Override
-  public void setTimestamp(int parameterIndex, Timestamp x) {
+  public void setTimestamp(int parameterIndex, Timestamp x) throws 
SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIMESTAMP);
+      return;
+    }
     ZonedDateTime zonedDateTime =
         ZonedDateTime.ofInstant(Instant.ofEpochMilli(x.getTime()), 
super.zoneId);
     this.parameters.put(
@@ -989,6 +1037,10 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
 
   @Override
   public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) 
throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIMESTAMP);
+      return;
+    }
     ZonedDateTime zonedDateTime = null;
     if (cal != null) {
       zonedDateTime =
@@ -1024,7 +1076,8 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
       if (!parameters.containsKey(i)) {
         throw new SQLException(String.format(JdbcMessages.PARAMETER_UNSET, i));
       }
-      newSql.append(parameters.get(i));
+      String parameter = parameters.get(i);
+      newSql.append(parameter == null ? "NULL" : parameter);
       newSql.append(parts.get(i));
     }
     return newSql.toString();
diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBSQLException.java 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBSQLException.java
index 540ae847b54..a75a54862fa 100644
--- 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBSQLException.java
+++ 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBSQLException.java
@@ -31,6 +31,10 @@ public class IoTDBSQLException extends SQLException {
     super(reason);
   }
 
+  public IoTDBSQLException(String reason, Throwable cause) {
+    super(reason, cause);
+  }
+
   public IoTDBSQLException(String reason, TSStatus status) {
     super(reason, status.message, status.code);
   }
diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatement.java
 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatement.java
index 79312bc5f84..998d053fb06 100644
--- 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatement.java
+++ 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatement.java
@@ -272,12 +272,14 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
       }
 
       @Override
-      public int isNullable(int param) {
+      public int isNullable(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return ParameterMetaData.parameterNullableUnknown;
       }
 
       @Override
-      public boolean isSigned(int param) {
+      public boolean isSigned(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         if (!serverSidePrepared) {
           return false;
         }
@@ -289,17 +291,20 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
       }
 
       @Override
-      public int getPrecision(int param) {
+      public int getPrecision(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return 0;
       }
 
       @Override
-      public int getScale(int param) {
+      public int getScale(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return 0;
       }
 
       @Override
-      public int getParameterType(int param) {
+      public int getParameterType(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         if (!serverSidePrepared) {
           return Types.NULL;
         }
@@ -307,17 +312,20 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
       }
 
       @Override
-      public String getParameterTypeName(int param) {
+      public String getParameterTypeName(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return null;
       }
 
       @Override
-      public String getParameterClassName(int param) {
+      public String getParameterClassName(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return null;
       }
 
       @Override
-      public int getParameterMode(int param) {
+      public int getParameterMode(int param) throws SQLException {
+        checkParameterMetadataIndex(param);
         return ParameterMetaData.parameterModeIn;
       }
 
@@ -393,6 +401,10 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
 
   @Override
   public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.BINARY);
+      return;
+    }
     checkParameterIndex(parameterIndex);
     setPreparedParameterValue(parameterIndex, x, Types.BINARY);
     // Format as hexadecimal string literal for SQL: X'0A0B0C'
@@ -401,6 +413,10 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
 
   @Override
   public void setDate(int parameterIndex, Date x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.DATE);
+      return;
+    }
     checkParameterIndex(parameterIndex);
     // Use ISO-8601 format YYYY-MM-DD for DATE columns
     String dateStr = x.toLocalDate().toString();
@@ -415,6 +431,10 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
 
   @Override
   public void setTime(int parameterIndex, Time x) throws SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIME);
+      return;
+    }
     checkParameterIndex(parameterIndex);
     try {
       long time = x.getTime();
@@ -445,6 +465,10 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
 
   @Override
   public void setTimestamp(int parameterIndex, Timestamp x) throws 
SQLException {
+    if (x == null) {
+      setNull(parameterIndex, Types.TIMESTAMP);
+      return;
+    }
     checkParameterIndex(parameterIndex);
     // Use millisecond value for SQL compatibility with TIMESTAMP columns
     long timestampMs = x.getTime();
@@ -510,6 +534,13 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
     }
   }
 
+  private void checkParameterMetadataIndex(int index) throws SQLException {
+    if (index < 1 || index > parameterCount) {
+      throw new SQLException(
+          "Parameter index out of range: " + index + " (expected 1-" + 
parameterCount + ")");
+    }
+  }
+
   private void setPreparedParameterValue(int parameterIndex, Object value, int 
sqlType) {
     if (!serverSidePrepared) {
       return;
@@ -554,6 +585,13 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
 
   @Override
   public void setBinaryStream(int parameterIndex, InputStream x, int length) 
throws SQLException {
+    if (length < 0) {
+      throw new SQLException("length must be >= 0");
+    }
+    if (x == null) {
+      setNull(parameterIndex, Types.BINARY);
+      return;
+    }
     try {
       byte[] bytes = ReadWriteIOUtils.readBytes(x, length);
       setBytes(parameterIndex, bytes);
@@ -692,7 +730,8 @@ public class IoTDBTablePreparedStatement extends 
IoTDBStatement implements Prepa
       if (!parameters.containsKey(i)) {
         throw new SQLException(String.format(JdbcMessages.PARAMETER_UNSET, i));
       }
-      newSql.append(parameters.get(i));
+      String parameter = parameters.get(i);
+      newSql.append(parameter == null ? "NULL" : parameter);
       newSql.append(parts.get(i));
     }
     return newSql.toString();
diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
index d5eb1d720a4..5957421d949 100644
--- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
+++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Utils.java
@@ -170,6 +170,21 @@ public class Utils {
       String key = tmpParam.substring(0, separatorIndex);
       String value = tmpParam.substring(separatorIndex + 1);
       switch (key) {
+        case Config.AUTH_USER:
+        case Config.AUTH_PASSWORD:
+          info.put(key, value);
+          break;
+        case Config.DEFAULT_BUFFER_CAPACITY:
+        case Config.THRIFT_FRAME_MAX_SIZE:
+          try {
+            if (Integer.parseInt(value) <= 0) {
+              return false;
+            }
+          } catch (NumberFormatException e) {
+            return false;
+          }
+          info.put(key, value);
+          break;
         case RPC_COMPRESS:
           if (isBoolean(value)) {
             info.put(key, value);
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBConnectionTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBConnectionTest.java
index 230cb2c9b50..2064a4027f6 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBConnectionTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBConnectionTest.java
@@ -46,8 +46,11 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class IoTDBConnectionTest {
@@ -94,6 +97,43 @@ public class IoTDBConnectionTest {
     assertEquals(connection.getTimeZone(), timeZone);
   }
 
+  @Test
+  public void testSetTimeZoneRejectsInvalidZoneBeforeRpc() throws TException {
+    connection.setClient(client);
+
+    assertThrows(IoTDBSQLException.class, () -> 
connection.setTimeZone("invalid-zone"));
+    verify(client, never()).setTimeZone(any(TSSetTimeZoneReq.class));
+  }
+
+  @Test
+  public void testSetClientInfoWrapsInvalidTimeZone() {
+    SQLClientInfoException exception =
+        assertThrows(
+            SQLClientInfoException.class,
+            () -> connection.setClientInfo("time_zone", "invalid-zone"));
+
+    assertTrue(exception.getCause() instanceof IoTDBSQLException);
+  }
+
+  @Test
+  public void testSetClientInfoRejectsNullNameWithoutNpe() {
+    assertThrows(SQLClientInfoException.class, () -> 
connection.setClientInfo(null, "value"));
+  }
+
+  @Test
+  public void testTableCatalogAndSchemaRejectNull() {
+    IoTDBConnection tableConnection =
+        new IoTDBConnection() {
+          @Override
+          public String getSqlDialect() {
+            return Constant.TABLE_DIALECT;
+          }
+        };
+
+    assertThrows(SQLException.class, () -> tableConnection.setCatalog(null));
+    assertThrows(SQLException.class, () -> tableConnection.setSchema(null));
+  }
+
   @Test
   public void testGetServerProperties() throws TException {
     final String version = "v0.1";
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactoryTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactoryTest.java
index e9e9a336058..724917d9081 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactoryTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactoryTest.java
@@ -103,6 +103,31 @@ public class IoTDBDataSourceFactoryTest {
     assertEquals("reader", dataSource.getRoleName());
   }
 
+  @Test
+  public void testCreateDataSourceAcceptsIoTDBConnectionProperties() throws 
SQLException {
+    Properties properties = new Properties();
+    properties.setProperty(Config.NETWORK_TIMEOUT, "1234");
+    properties.setProperty(Config.TIME_ZONE, "Asia/Shanghai");
+    properties.setProperty(Config.CHARSET, "UTF-8");
+    properties.setProperty(Config.USE_SSL, "true");
+    properties.setProperty(Config.TRUST_STORE, "trust-store");
+    properties.setProperty(Config.TRUST_STORE_PWD, "trust-store-password");
+    properties.setProperty(Config.SQL_DIALECT, Constant.TABLE);
+    properties.setProperty(Utils.RPC_COMPRESS, "true");
+
+    IoTDBDataSource dataSource =
+        (IoTDBDataSource) new 
IoTDBDataSourceFactory().createDataSource(properties);
+
+    assertEquals("1234", 
dataSource.getConnectionProperty(Config.NETWORK_TIMEOUT));
+    assertEquals("Asia/Shanghai", 
dataSource.getConnectionProperty(Config.TIME_ZONE));
+    assertEquals("UTF-8", dataSource.getConnectionProperty(Config.CHARSET));
+    assertEquals("true", dataSource.getConnectionProperty(Config.USE_SSL));
+    assertEquals("trust-store", 
dataSource.getConnectionProperty(Config.TRUST_STORE));
+    assertEquals("trust-store-password", 
dataSource.getConnectionProperty(Config.TRUST_STORE_PWD));
+    assertEquals(Constant.TABLE, 
dataSource.getConnectionProperty(Config.SQL_DIALECT));
+    assertEquals("true", dataSource.getConnectionProperty(Utils.RPC_COMPRESS));
+  }
+
   @Test(expected = SQLException.class)
   public void testCreateDataSourceRejectsInvalidStandardPortProperty() throws 
SQLException {
     Properties properties = new Properties();
@@ -111,6 +136,14 @@ public class IoTDBDataSourceFactoryTest {
     new IoTDBDataSourceFactory().createDataSource(properties);
   }
 
+  @Test(expected = SQLException.class)
+  public void testCreateDataSourceRejectsUnknownBeanProperty() throws 
SQLException {
+    Properties properties = new Properties();
+    properties.setProperty("unknownProperty", "value");
+
+    new IoTDBDataSourceFactory().createDataSource(properties);
+  }
+
   @Test
   public void testDataSourceExplicitUrlTakesPrecedenceOverStandardProperties() 
{
     IoTDBDataSource dataSource = new IoTDBDataSource();
@@ -144,6 +177,11 @@ public class IoTDBDataSourceFactoryTest {
     assertEquals(Integer.valueOf(6667), dataSource.getPort());
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void testDataSourceRejectsInvalidDirectPort() {
+    new IoTDBDataSource().setPortNumber(65536);
+  }
+
   @Test
   public void testDataSourceWrapperMethods() throws SQLException {
     IoTDBDataSource dataSource = new IoTDBDataSource();
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDriverTest.java 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDriverTest.java
index fb44d3664ec..7a9173186c1 100644
--- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDriverTest.java
+++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDriverTest.java
@@ -99,9 +99,12 @@ public class IoTDBDriverTest {
     try {
       DriverPropertyInfo[] propertyInfos =
           driver.getPropertyInfo(
-              
"jdbc:iotdb://localhost:6667?use_ssl=true&sql_dialect=table&network_timeout=123&rpc_compress=true",
+              
"jdbc:iotdb://localhost:6667?user=url-user&thrift_default_buffer_capacity=1024&thrift_max_frame_size=2048&use_ssl=true&sql_dialect=table&network_timeout=123&rpc_compress=true",
               properties);
 
+      assertEquals("url-user", findProperty(propertyInfos, 
Config.AUTH_USER).value);
+      assertEquals("1024", findProperty(propertyInfos, 
Config.DEFAULT_BUFFER_CAPACITY).value);
+      assertEquals("2048", findProperty(propertyInfos, 
Config.THRIFT_FRAME_MAX_SIZE).value);
       assertEquals("true", findProperty(propertyInfos, Config.USE_SSL).value);
       assertEquals(Constant.TABLE, findProperty(propertyInfos, 
Config.SQL_DIALECT).value);
       assertEquals("123", findProperty(propertyInfos, 
Config.NETWORK_TIMEOUT).value);
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
index acc6b523b5e..3d9e8154e8b 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
@@ -31,8 +31,12 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Date;
 import java.sql.ParameterMetaData;
 import java.sql.SQLException;
+import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
 import java.time.ZoneId;
@@ -98,6 +102,24 @@ public class IoTDBPreparedStatementTest {
     assertThrows(SQLException.class, () -> metadata.unwrap(String.class));
   }
 
+  @SuppressWarnings("resource")
+  @Test
+  public void testParameterMetadataRejectsUnsetIndexAndHandlesNullValue() 
throws Exception {
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", 
zoneId);
+    ParameterMetaData metadata = ps.getParameterMetaData();
+
+    assertEquals(0, metadata.getParameterCount());
+    assertThrows(SQLException.class, () -> metadata.getPrecision(1));
+
+    ps.setString(1, null);
+
+    assertEquals(1, metadata.getParameterCount());
+    assertEquals(0, metadata.getPrecision(1));
+    assertEquals(Types.NULL, metadata.getParameterType(1));
+    assertEquals(ParameterMetaData.parameterModeUnknown, 
metadata.getParameterMode(1));
+  }
+
   @SuppressWarnings("resource")
   @Test
   public void unusedArgument() throws SQLException {
@@ -253,6 +275,40 @@ public class IoTDBPreparedStatementTest {
         "SELECT status, 'temperature' FROM root.ln.wf01.wt01", 
argument.getValue().getStatement());
   }
 
+  @SuppressWarnings("resource")
+  @Test
+  public void nullArgumentsUseSqlNullLiteral() throws Exception {
+    String sql = "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f,g) 
VALUES(1,?,?,?,?,?,?,?)";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+
+    ps.setString(1, null);
+    ps.setObject(2, null);
+    ps.setDate(3, (Date) null);
+    ps.setTime(4, (Time) null);
+    ps.setTimestamp(5, (Timestamp) null);
+    ps.setBytes(6, null);
+    ps.setBinaryStream(7, (InputStream) null, 0);
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f,g) 
VALUES(1,NULL,NULL,NULL,NULL,NULL,NULL,NULL)",
+        argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void setBinaryStreamRejectsNegativeLength() throws Exception {
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", 
zoneId);
+
+    assertThrows(
+        SQLException.class, () -> ps.setBinaryStream(1, new 
ByteArrayInputStream(new byte[0]), -1));
+  }
+
   @SuppressWarnings("resource")
   @Test
   public void oneTimeLongArgument() throws Exception {
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatementTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatementTest.java
index 80aa9fa32af..f3422a01638 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatementTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBTablePreparedStatementTest.java
@@ -21,8 +21,11 @@ package org.apache.iotdb.jdbc;
 
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.rpc.stmt.PreparedParameterSerde;
+import org.apache.iotdb.rpc.stmt.PreparedParameterSerde.DeserializedParam;
 import org.apache.iotdb.service.rpc.thrift.IClientRPCService.Iface;
 import org.apache.iotdb.service.rpc.thrift.TSExecutePreparedReq;
+import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp;
 import org.apache.iotdb.service.rpc.thrift.TSPrepareReq;
 import org.apache.iotdb.service.rpc.thrift.TSPrepareResp;
@@ -35,10 +38,19 @@ import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.ParameterMetaData;
 import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
 import java.time.ZoneId;
+import java.util.List;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThrows;
@@ -86,6 +98,7 @@ public class IoTDBTablePreparedStatementTest {
     // Mock for executePreparedStatement
     when(client.executePreparedStatement(any(TSExecutePreparedReq.class)))
         .thenReturn(execStatementResp);
+    
when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(execStatementResp);
   }
 
   /** Count the number of '?' placeholders in a SQL string, ignoring those 
inside quotes */
@@ -130,6 +143,23 @@ public class IoTDBTablePreparedStatementTest {
     assertThrows(SQLException.class, () -> metadata.unwrap(String.class));
   }
 
+  @SuppressWarnings("resource")
+  @Test
+  public void testParameterMetadataRejectsInvalidIndex() throws Exception {
+    IoTDBTablePreparedStatement ps =
+        new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT 
?", zoneId);
+    ParameterMetaData metadata = ps.getParameterMetaData();
+
+    assertEquals(1, metadata.getParameterCount());
+    assertThrows(SQLException.class, () -> metadata.getParameterType(0));
+    assertThrows(SQLException.class, () -> metadata.isSigned(2));
+
+    ps.setInt(1, 1);
+
+    assertEquals(Types.INTEGER, metadata.getParameterType(1));
+    assertTrue(metadata.isSigned(1));
+  }
+
   @SuppressWarnings("resource")
   @Test
   public void testTableModelLoginInjectionWithComment() throws Exception {
@@ -256,4 +286,58 @@ public class IoTDBTablePreparedStatementTest {
     verify(client).executePreparedStatement(argument.capture());
     assertTrue(argument.getValue().getParameters() != null);
   }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelPreparedNullSettersSerializeNulls() throws 
Exception {
+    String sql = "SELECT * FROM users WHERE a=? AND b=? AND c=? AND d=? AND 
e=? AND f=?";
+    IoTDBTablePreparedStatement ps =
+        new IoTDBTablePreparedStatement(connection, client, sessionId, sql, 
zoneId);
+
+    ps.setObject(1, null);
+    ps.setBytes(2, null);
+    ps.setDate(3, (Date) null);
+    ps.setTime(4, (Time) null);
+    ps.setTimestamp(5, (Timestamp) null);
+    ps.setBinaryStream(6, (InputStream) null, 0);
+    ps.execute();
+
+    ArgumentCaptor<TSExecutePreparedReq> argument =
+        ArgumentCaptor.forClass(TSExecutePreparedReq.class);
+    verify(client).executePreparedStatement(argument.capture());
+    List<DeserializedParam> parameters =
+        
PreparedParameterSerde.deserialize(ByteBuffer.wrap(argument.getValue().getParameters()));
+
+    assertEquals(6, parameters.size());
+    for (DeserializedParam parameter : parameters) {
+      assertTrue(parameter.isNull());
+    }
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelSetBinaryStreamRejectsNegativeLength() throws 
Exception {
+    IoTDBTablePreparedStatement ps =
+        new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT 
?", zoneId);
+
+    assertThrows(
+        SQLException.class, () -> ps.setBinaryStream(1, new 
ByteArrayInputStream(new byte[0]), -1));
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelClientSideNullStringUsesSqlNullLiteral() throws 
Exception {
+    String sql = "INSERT INTO users(time,email) VALUES(1, ?)";
+    IoTDBTablePreparedStatement ps =
+        new IoTDBTablePreparedStatement(connection, client, sessionId, sql, 
zoneId);
+
+    ps.setString(1, null);
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "INSERT INTO users(time,email) VALUES(1, NULL)", 
argument.getValue().getStatement());
+  }
 }
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
index e5b1de15da8..fbc7fe4831a 100644
--- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
+++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/UtilsTest.java
@@ -130,6 +130,25 @@ public class UtilsTest {
     assertEquals(123, params.getNetworkTimeout());
   }
 
+  @Test
+  public void testParseUrlAppliesCredentialAndBufferQueryProperties() throws 
IoTDBURLException {
+    Properties properties = new Properties();
+    properties.setProperty(Config.AUTH_USER, "prop-user");
+    properties.setProperty(Config.DEFAULT_BUFFER_CAPACITY, "1");
+
+    IoTDBConnectionParams params =
+        Utils.parseUrl(
+            
"jdbc:iotdb://test:6667?user=url-user&password=url-password&thrift_default_buffer_capacity=1024&thrift_max_frame_size=2048",
+            properties);
+
+    assertEquals("url-user", params.getUsername());
+    assertEquals("url-password", params.getPassword());
+    assertEquals(1024, params.getThriftDefaultBufferSize());
+    assertEquals(2048, params.getThriftMaxFrameSize());
+    assertEquals("url-user", properties.getProperty(Config.AUTH_USER));
+    assertEquals("1024", 
properties.getProperty(Config.DEFAULT_BUFFER_CAPACITY));
+  }
+
   @Test
   public void testParseUrlAllowsEmptyDatabaseBeforeQuery() throws 
IoTDBURLException {
     IoTDBConnectionParams params =
@@ -261,6 +280,17 @@ public class UtilsTest {
     Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?network_timeout=-1", new 
Properties());
   }
 
+  @Test(expected = IoTDBURLException.class)
+  public void testParseUrlParamRejectsInvalidThriftBufferCapacityValue() 
throws IoTDBURLException {
+    Utils.parseUrl(
+        "jdbc:iotdb://127.0.0.1:6667?thrift_default_buffer_capacity=0", new 
Properties());
+  }
+
+  @Test(expected = IoTDBURLException.class)
+  public void testParseUrlParamRejectsInvalidThriftFrameMaxSizeValue() throws 
IoTDBURLException {
+    Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?thrift_max_frame_size=bad", 
new Properties());
+  }
+
   @Test(expected = IoTDBURLException.class)
   public void testParseUrlParamRejectsInvalidSqlDialectValue() throws 
IoTDBURLException {
     Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?sql_dialect=bad", new 
Properties());


Reply via email to