diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java index 8c816f61fcda7..83ae827de38ab 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java @@ -1314,31 +1314,26 @@ public void testUseSameStatement() throws SQLException { statement.execute("insert into root.sg1.d1(timestamp,s0,s1) values(1000,1000,1000)"); statement.execute("insert into root.sg1.d0(timestamp,s0,s1) values(10,10,10)"); - List resultSetList = new ArrayList<>(); - - ResultSet r1 = statement.executeQuery("select * from root.sg1.d0 where time <= 1"); - resultSetList.add(r1); - - ResultSet r2 = statement.executeQuery("select * from root.sg1.d1 where s0 == 1000"); - resultSetList.add(r2); - - ResultSet r3 = statement.executeQuery("select * from root.sg1.d0 where s1 == 10"); - resultSetList.add(r3); - - r1.next(); - Assert.assertEquals(r1.getLong(1), 1L); - Assert.assertEquals(r1.getLong(2), 1L); - Assert.assertEquals(r1.getLong(3), 1L); + try (ResultSet r1 = statement.executeQuery("select * from root.sg1.d0 where time <= 1")) { + Assert.assertTrue(r1.next()); + Assert.assertEquals(r1.getLong(1), 1L); + Assert.assertEquals(r1.getLong(2), 1L); + Assert.assertEquals(r1.getLong(3), 1L); + } - r2.next(); - Assert.assertEquals(r2.getLong(1), 1000L); - Assert.assertEquals(r2.getLong(2), 1000L); - Assert.assertEquals(r2.getLong(3), 1000L); + try (ResultSet r2 = statement.executeQuery("select * from root.sg1.d1 where s0 == 1000")) { + Assert.assertTrue(r2.next()); + Assert.assertEquals(r2.getLong(1), 1000L); + Assert.assertEquals(r2.getLong(2), 1000L); + Assert.assertEquals(r2.getLong(3), 1000L); + } - r3.next(); - Assert.assertEquals(r3.getLong(1), 10L); - Assert.assertEquals(r3.getLong(2), 10L); - Assert.assertEquals(r3.getLong(3), 10L); + try (ResultSet r3 = statement.executeQuery("select * from root.sg1.d0 where s1 == 10")) { + Assert.assertTrue(r3.next()); + Assert.assertEquals(r3.getLong(1), 10L); + Assert.assertEquals(r3.getLong(2), 10L); + Assert.assertEquals(r3.getLong(3), 10L); + } } finally { executeQuietly(statement, "DELETE DATABASE root.sg1"); } diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/IoTDBSimpleQueryTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/IoTDBSimpleQueryTableIT.java index f8cc92699e765..1e96df0e5d3c8 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/IoTDBSimpleQueryTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/old/IoTDBSimpleQueryTableIT.java @@ -577,31 +577,29 @@ public void testUseSameStatement() throws SQLException { statement.execute("insert into table1(time,device,s0,s1) values(1000,'d1',1000,1000)"); statement.execute("insert into table1(time,device,s0,s1) values(10,'d0',10,10)"); - List resultSetList = new ArrayList<>(); - - ResultSet r1 = statement.executeQuery("select * from table1 where device='d0' and time <= 1"); - resultSetList.add(r1); - - ResultSet r2 = statement.executeQuery("select * from table1 where device='d1' and s0 = 1000"); - resultSetList.add(r2); - - ResultSet r3 = statement.executeQuery("select * from table1 where device='d0' and s1 = 10"); - resultSetList.add(r3); - - r1.next(); - Assert.assertEquals(r1.getLong(1), 1L); - Assert.assertEquals(r1.getLong(3), 1L); - Assert.assertEquals(r1.getLong(4), 1L); + try (ResultSet r1 = + statement.executeQuery("select * from table1 where device='d0' and time <= 1")) { + Assert.assertTrue(r1.next()); + Assert.assertEquals(r1.getLong(1), 1L); + Assert.assertEquals(r1.getLong(3), 1L); + Assert.assertEquals(r1.getLong(4), 1L); + } - r2.next(); - Assert.assertEquals(r2.getLong(1), 1000L); - Assert.assertEquals(r2.getLong(3), 1000L); - Assert.assertEquals(r2.getLong(4), 1000L); + try (ResultSet r2 = + statement.executeQuery("select * from table1 where device='d1' and s0 = 1000")) { + Assert.assertTrue(r2.next()); + Assert.assertEquals(r2.getLong(1), 1000L); + Assert.assertEquals(r2.getLong(3), 1000L); + Assert.assertEquals(r2.getLong(4), 1000L); + } - r3.next(); - Assert.assertEquals(r3.getLong(1), 10L); - Assert.assertEquals(r3.getLong(3), 10L); - Assert.assertEquals(r3.getLong(4), 10L); + try (ResultSet r3 = + statement.executeQuery("select * from table1 where device='d0' and s1 = 10")) { + Assert.assertTrue(r3.next()); + Assert.assertEquals(r3.getLong(1), 10L); + Assert.assertEquals(r3.getLong(3), 10L); + Assert.assertEquals(r3.getLong(4), 10L); + } } } diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/view/old/IoTDBSimpleQueryTableViewIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/view/old/IoTDBSimpleQueryTableViewIT.java index 91f4998ac537b..0870d2bb89adb 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/view/old/IoTDBSimpleQueryTableViewIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/view/old/IoTDBSimpleQueryTableViewIT.java @@ -40,10 +40,8 @@ import java.sql.Statement; import java.sql.Types; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import java.util.List; import static org.apache.iotdb.db.it.utils.TestUtils.defaultFormatDataTime; import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest; @@ -453,31 +451,29 @@ public void testUseSameStatement() throws SQLException { statement.execute( "CREATE VIEW table1(device STRING TAG, s0 INT64 FIELD, s1 INT64 FIELD) as root.sg1.**"); - List resultSetList = new ArrayList<>(); - - ResultSet r1 = statement.executeQuery("select * from table1 where device='d0' and time <= 1"); - resultSetList.add(r1); - - ResultSet r2 = statement.executeQuery("select * from table1 where device='d1' and s0 = 1000"); - resultSetList.add(r2); - - ResultSet r3 = statement.executeQuery("select * from table1 where device='d0' and s1 = 10"); - resultSetList.add(r3); - - r1.next(); - Assert.assertEquals(r1.getLong(1), 1L); - Assert.assertEquals(r1.getLong(3), 1L); - Assert.assertEquals(r1.getLong(4), 1L); + try (ResultSet r1 = + statement.executeQuery("select * from table1 where device='d0' and time <= 1")) { + Assert.assertTrue(r1.next()); + Assert.assertEquals(r1.getLong(1), 1L); + Assert.assertEquals(r1.getLong(3), 1L); + Assert.assertEquals(r1.getLong(4), 1L); + } - r2.next(); - Assert.assertEquals(r2.getLong(1), 1000L); - Assert.assertEquals(r2.getLong(3), 1000L); - Assert.assertEquals(r2.getLong(4), 1000L); + try (ResultSet r2 = + statement.executeQuery("select * from table1 where device='d1' and s0 = 1000")) { + Assert.assertTrue(r2.next()); + Assert.assertEquals(r2.getLong(1), 1000L); + Assert.assertEquals(r2.getLong(3), 1000L); + Assert.assertEquals(r2.getLong(4), 1000L); + } - r3.next(); - Assert.assertEquals(r3.getLong(1), 10L); - Assert.assertEquals(r3.getLong(3), 10L); - Assert.assertEquals(r3.getLong(4), 10L); + try (ResultSet r3 = + statement.executeQuery("select * from table1 where device='d0' and s1 = 10")) { + Assert.assertTrue(r3.next()); + Assert.assertEquals(r3.getLong(1), 10L); + Assert.assertEquals(r3.getLong(3), 10L); + Assert.assertEquals(r3.getLong(4), 10L); + } } } diff --git a/iotdb-client/jdbc/src/main/i18n/en/org/apache/iotdb/jdbc/i18n/JdbcMessages.java b/iotdb-client/jdbc/src/main/i18n/en/org/apache/iotdb/jdbc/i18n/JdbcMessages.java index d5db57b4fce58..ba95a5525a7fd 100644 --- a/iotdb-client/jdbc/src/main/i18n/en/org/apache/iotdb/jdbc/i18n/JdbcMessages.java +++ b/iotdb-client/jdbc/src/main/i18n/en/org/apache/iotdb/jdbc/i18n/JdbcMessages.java @@ -23,7 +23,7 @@ public final class JdbcMessages { // IoTDBDriver public static final String REGISTER_DRIVER_ERROR = - "Error occurs when registering TsFile driver"; + "Error occurs when registering IoTDB driver"; public static final String METHOD_NOT_SUPPORTED = "Method not supported"; // StringUtils diff --git a/iotdb-client/jdbc/src/main/i18n/zh/org/apache/iotdb/jdbc/i18n/JdbcMessages.java b/iotdb-client/jdbc/src/main/i18n/zh/org/apache/iotdb/jdbc/i18n/JdbcMessages.java index 0cc824fd08446..7dfc2b10ac795 100644 --- a/iotdb-client/jdbc/src/main/i18n/zh/org/apache/iotdb/jdbc/i18n/JdbcMessages.java +++ b/iotdb-client/jdbc/src/main/i18n/zh/org/apache/iotdb/jdbc/i18n/JdbcMessages.java @@ -23,7 +23,7 @@ public final class JdbcMessages { // IoTDBDriver public static final String REGISTER_DRIVER_ERROR = - "注册 TsFile 驱动时发生错误"; + "注册 IoTDB 驱动时发生错误"; public static final String METHOD_NOT_SUPPORTED = "不支持此方法"; // StringUtils diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Config.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Config.java index 0b1049330eae7..781fb683852b0 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Config.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/Config.java @@ -39,7 +39,7 @@ private Config() { /** If host is provided, without a port. */ static final int IOTDB_DEFAULT_PORT = 6667; - /** TsFile's default series name. */ + /** Default series name. */ static final String DEFAULT_SERIES_NAME = "default"; static final String AUTH_USER = "user"; @@ -51,6 +51,9 @@ private Config() { static final int RETRY_NUM = 3; static final long RETRY_INTERVAL_MS = 1000; + static final int DRIVER_MAJOR_VERSION = 4; + static final int DRIVER_MINOR_VERSION = 3; + public static final int DEFAULT_FETCH_SIZE = 5000; static final int DEFAULT_CONNECTION_TIMEOUT_MS = 0; diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBAbstractDatabaseMetadata.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBAbstractDatabaseMetadata.java index 0f81bca6069aa..40144e338814e 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBAbstractDatabaseMetadata.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBAbstractDatabaseMetadata.java @@ -539,17 +539,20 @@ public boolean allTablesAreSelectable() throws SQLException { @Override public String getURL() throws SQLException { + checkConnectionOpen(); // TODO: Return the URL for this DBMS or null if it cannot be generated return this.connection.getUrl(); } @Override public String getUserName() throws SQLException { + checkConnectionOpen(); return connection.getUserName(); } @Override public boolean isReadOnly() throws SQLException { + checkConnectionOpen(); try { return client.getProperties().isReadOnly; } catch (TException e) { @@ -585,6 +588,7 @@ public String getDatabaseProductName() throws SQLException { @Override public String getDatabaseProductVersion() throws SQLException { + checkConnectionOpen(); String serverVersion = ""; String sql = "SHOW VERSION"; try (Statement stmt = this.connection.createStatement(); @@ -606,12 +610,12 @@ public String getDriverName() throws SQLException { @Override public int getDriverMajorVersion() { - return 4; + return Config.DRIVER_MAJOR_VERSION; } @Override public int getDriverMinorVersion() { - return 3; + return Config.DRIVER_MINOR_VERSION; } @Override @@ -681,6 +685,7 @@ public String getStringFunctions() throws SQLException { @Override public String getSystemFunctions() throws SQLException { + checkConnectionOpen(); String result = ""; Statement statement = null; ResultSet resultSet = null; @@ -1037,6 +1042,7 @@ public int getMaxColumnsInTable() throws SQLException { @Override public int getMaxConnections() throws SQLException { + checkConnectionOpen(); int maxcount = 0; try { maxcount = client.getProperties().getMaxConcurrentClientNum(); @@ -1083,6 +1089,7 @@ public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { @Override public int getMaxStatementLength() throws SQLException { + checkConnectionOpen(); try { return client.getProperties().getThriftMaxFrameSize(); } catch (TException e) { @@ -2284,6 +2291,7 @@ public ResultSet getUDTs( @Override public Connection getConnection() throws SQLException { + checkConnectionOpen(); return connection; } @@ -2462,6 +2470,7 @@ public int getResultSetHoldability() throws SQLException { @Override public int getDatabaseMajorVersion() throws SQLException { + checkConnectionOpen(); int majorVersion = 0; try { String version = client.getProperties().getVersion(); @@ -2477,6 +2486,7 @@ public int getDatabaseMajorVersion() throws SQLException { @Override public int getDatabaseMinorVersion() throws SQLException { + checkConnectionOpen(); int minorVersion = 0; try { String version = client.getProperties().getVersion(); @@ -2858,11 +2868,20 @@ public boolean generatedKeyAlwaysReturned() throws SQLException { @Override public T unwrap(Class arg0) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + checkConnectionOpen(); + return JdbcWrapperUtils.unwrap(this, arg0); } @Override public boolean isWrapperFor(Class arg0) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + checkConnectionOpen(); + return JdbcWrapperUtils.isWrapperFor(this, arg0); + } + + protected final void checkConnectionOpen() throws SQLException { + if (connection == null || connection.isClosed()) { + throw new SQLException( + String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, "get metadata")); + } } } 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 7f37e2206fb87..8af5bcd245ac8 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,12 +60,17 @@ 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; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; public class IoTDBConnection implements Connection { @@ -82,6 +87,8 @@ public class IoTDBConnection implements Connection { private boolean isClosed = true; private SQLWarning warningChain = null; private TTransport transport; + private final Set openStatements = + Collections.newSetFromMap(new ConcurrentHashMap()); /** * Timeout of query can be set by users. Unit: s If not set, default value 0 will be used, which @@ -127,18 +134,15 @@ public IoTDBConnection(String url, Properties info) throws SQLException, TTransp if (url == null) { throw new IoTDBURLException(JdbcMessages.INPUT_URL_NULL); } - params = Utils.parseUrl(url, info); + Properties properties = info == null ? new Properties() : info; + params = Utils.parseUrl(url, properties); this.url = url; - this.userName = info.get("user").toString(); + this.userName = params.getUsername(); this.networkTimeout = params.getNetworkTimeout(); this.zoneId = ZoneId.of(params.getTimeZone()); this.charset = params.getCharset(); openTransport(); - if (Config.rpcThriftCompressionEnable) { - setClient(new IClientRPCService.Client(new TCompactProtocol(transport))); - } else { - setClient(new IClientRPCService.Client(new TBinaryProtocol(transport))); - } + openClient(); // open client session openSession(); // Wrap the client with a thread-safe proxy to serialize the RPC calls @@ -156,21 +160,25 @@ public IoTDBConnectionParams getParams() { @Override public boolean isWrapperFor(Class arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_IS_WRAPPER_FOR); + checkOpen("isWrapperFor"); + return JdbcWrapperUtils.isWrapperFor(this, arg0); } @Override public T unwrap(Class arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_UNWRAP); + checkOpen("unwrap"); + return JdbcWrapperUtils.unwrap(this, arg0); } @Override public void abort(Executor arg0) throws SQLException { + checkOpen("abort"); throw new SQLException(JdbcMessages.NOT_SUPPORT_ABORT); } @Override - public void clearWarnings() { + public void clearWarnings() throws SQLException { + checkOpen("clearWarnings"); warningChain = null; } @@ -179,59 +187,77 @@ public void close() throws SQLException { if (isClosed) { return; } + SQLException statementCloseException = closeOpenStatements(); TSCloseSessionReq req = new TSCloseSessionReq(sessionId); try { getClient().closeSession(req); } catch (TException e) { - throw new SQLException( - "Error occurs when closing session at server. Maybe server is down.", e); + SQLException closeSessionException = + new SQLException("Error occurs when closing session at server. Maybe server is down.", e); + if (statementCloseException != null) { + closeSessionException.addSuppressed(statementCloseException); + } + throw closeSessionException; } finally { isClosed = true; + openStatements.clear(); if (transport != null) { transport.close(); } } + if (statementCloseException != null) { + throw statementCloseException; + } } @Override - public void commit() throws SQLException {} + public void commit() throws SQLException { + checkOpen("commit"); + } @Override public Array createArrayOf(String arg0, Object[] arg1) throws SQLException { + checkOpen("createArrayOf"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_ARRAY_OF); } @Override public Blob createBlob() throws SQLException { + checkOpen("createBlob"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_BLOB); } @Override public Clob createClob() throws SQLException { + checkOpen("createClob"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_CLOB); } @Override public NClob createNClob() throws SQLException { + checkOpen("createNClob"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_NCLOB); } @Override public SQLXML createSQLXML() throws SQLException { + checkOpen("createSQLXML"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_SQLXML); } @Override public Statement createStatement() throws SQLException { - if (isClosed) { - throw new SQLException(JdbcMessages.CANNOT_CREATE_STATEMENT_CLOSED); - } - return new IoTDBStatement(this, getClient(), sessionId, zoneId, charset, queryTimeout); + checkOpen("createStatement"); + IoTDBStatement statement = + new IoTDBStatement(this, getClient(), sessionId, zoneId, charset, queryTimeout); + registerStatement(statement); + return statement; } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + checkOpen("createStatement"); if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) { throw new SQLException( String.format( @@ -241,37 +267,49 @@ public Statement createStatement(int resultSetType, int resultSetConcurrency) throw new SQLException( String.format("Statements with ResultSet type %d are not supported", resultSetType)); } - return new IoTDBStatement(this, getClient(), sessionId, zoneId, charset, queryTimeout); + IoTDBStatement statement = + new IoTDBStatement(this, getClient(), sessionId, zoneId, charset, queryTimeout); + registerStatement(statement); + return statement; } @Override public Statement createStatement(int arg0, int arg1, int arg2) throws SQLException { + checkOpen("createStatement"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_STATEMENT); } @Override public Struct createStruct(String arg0, Object[] arg1) throws SQLException { + checkOpen("createStruct"); throw new SQLException(JdbcMessages.NOT_SUPPORT_CREATE_STRUCT); } @Override - public boolean getAutoCommit() { + public boolean getAutoCommit() throws SQLException { + checkOpen("getAutoCommit"); return autoCommit; } @Override - public void setAutoCommit(boolean arg0) { + public void setAutoCommit(boolean arg0) throws SQLException { + checkOpen("setAutoCommit"); autoCommit = arg0; } @Override - public String getCatalog() { + public String getCatalog() throws SQLException { + checkOpen("getCatalog"); return APACHE_IOTDB; } @Override public void setCatalog(String arg0) throws SQLException { + checkOpen("setCatalog"); if (getSqlDialect().equals(Constant.TABLE_DIALECT)) { + if (arg0 == null) { + throw new SQLException("catalog cannot be null"); + } if (APACHE_IOTDB.equals(arg0)) { return; } @@ -281,12 +319,10 @@ public void setCatalog(String arg0) throws SQLException { } } - PreparedStatement stmt = this.prepareStatement("USE ?"); - stmt.setString(1, arg0); - try { + try (PreparedStatement stmt = this.prepareStatement("USE ?")) { + stmt.setString(1, arg0); stmt.execute(); } catch (SQLException e) { - stmt.close(); logger.error(JdbcMessages.USE_DATABASE_ERROR, e.getMessage()); throw e; } @@ -295,34 +331,37 @@ public void setCatalog(String arg0) throws SQLException { @Override public Properties getClientInfo() throws SQLException { + checkOpen("getClientInfo"); throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_CLIENT_INFO); } @Override public void setClientInfo(Properties arg0) throws SQLClientInfoException { + checkOpenForClientInfo("setClientInfo"); throw new SQLClientInfoException(JdbcMessages.NOT_SUPPORT_SET_CLIENT_INFO, null); } @Override public String getClientInfo(String arg0) throws SQLException { + checkOpen("getClientInfo"); throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_CLIENT_INFO); } @Override - public int getHoldability() { - return 0; + public int getHoldability() throws SQLException { + checkOpen("getHoldability"); + return ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override public void setHoldability(int arg0) throws SQLException { + checkOpen("setHoldability"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_HOLDABILITY); } @Override public DatabaseMetaData getMetaData() throws SQLException { - if (isClosed) { - throw new SQLException(JdbcMessages.CANNOT_CREATE_STATEMENT_CLOSED); - } + checkOpen("getMetaData"); if (getSqlDialect().equals(Constant.TABLE_DIALECT)) { return new IoTDBRelationalDatabaseMetadata(this, getClient(), sessionId, zoneId); } @@ -330,12 +369,14 @@ public DatabaseMetaData getMetaData() throws SQLException { } @Override - public int getNetworkTimeout() { + public int getNetworkTimeout() throws SQLException { + checkOpen("getNetworkTimeout"); return networkTimeout; } @Override public String getSchema() throws SQLException { + checkOpen("getSchema"); if (getSqlDialect().equals(Constant.TABLE_DIALECT)) { return getDatabase(); } @@ -344,20 +385,22 @@ public String getSchema() throws SQLException { @Override public void setSchema(String arg0) throws SQLException { + checkOpen("setSchema"); // 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 + "\""; } } - PreparedStatement stmt = this.prepareStatement("USE ?"); - stmt.setString(1, arg0); - try { + try (PreparedStatement stmt = this.prepareStatement("USE ?")) { + stmt.setString(1, arg0); stmt.execute(); } catch (SQLException e) { - stmt.close(); logger.error(JdbcMessages.USE_DATABASE_ERROR, e.getMessage()); throw e; } @@ -365,27 +408,32 @@ public void setSchema(String arg0) throws SQLException { } @Override - public int getTransactionIsolation() { + public int getTransactionIsolation() throws SQLException { + checkOpen("getTransactionIsolation"); return Connection.TRANSACTION_NONE; } @Override public void setTransactionIsolation(int arg0) throws SQLException { + checkOpen("setTransactionIsolation"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_TRANSACTION_ISOLATION); } @Override public Map> getTypeMap() throws SQLException { + checkOpen("getTypeMap"); throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_TYPE_MAP); } @Override public void setTypeMap(Map> arg0) throws SQLException { + checkOpen("setTypeMap"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_TYPE_MAP); } @Override - public SQLWarning getWarnings() { + public SQLWarning getWarnings() throws SQLException { + checkOpen("getWarnings"); return warningChain; } @@ -395,70 +443,91 @@ public boolean isClosed() { } @Override - public boolean isReadOnly() { + public boolean isReadOnly() throws SQLException { + checkOpen("isReadOnly"); return false; } @Override public void setReadOnly(boolean readonly) throws SQLException { + checkOpen("setReadOnly"); if (readonly) { throw new SQLException(JdbcMessages.NOT_SUPPORT_READ_ONLY); } } @Override - public boolean isValid(int arg0) { + public boolean isValid(int arg0) throws SQLException { + if (arg0 < 0) { + throw new SQLException("timeout must be >= 0"); + } return !isClosed; } @Override public String nativeSQL(String arg0) throws SQLException { + checkOpen("nativeSQL"); throw new SQLException(JdbcMessages.NOT_SUPPORT_NATIVE_SQL); } @Override public CallableStatement prepareCall(String arg0) throws SQLException { + checkOpen("prepareCall"); throw new SQLException(NOT_SUPPORT_PREPARE_CALL); } @Override public CallableStatement prepareCall(String arg0, int arg1, int arg2) throws SQLException { + checkOpen("prepareCall"); throw new SQLException(NOT_SUPPORT_PREPARE_CALL); } @Override public CallableStatement prepareCall(String arg0, int arg1, int arg2, int arg3) throws SQLException { + checkOpen("prepareCall"); throw new SQLException(NOT_SUPPORT_PREPARE_CALL); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { + checkOpen("prepareStatement"); + if (sql == null) { + throw new SQLException("SQL statement cannot be null"); + } + IoTDBStatement statement; if (getSqlDialect().equals(Constant.TABLE_DIALECT)) { - return new IoTDBTablePreparedStatement(this, getClient(), sessionId, sql, zoneId, charset); + statement = + new IoTDBTablePreparedStatement(this, getClient(), sessionId, sql, zoneId, charset); } else { - return new IoTDBPreparedStatement(this, getClient(), sessionId, sql, zoneId, charset); + statement = new IoTDBPreparedStatement(this, getClient(), sessionId, sql, zoneId, charset); } + registerStatement(statement); + return (PreparedStatement) statement; } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + checkOpen("prepareStatement"); throw new SQLException(NOT_SUPPORT_PREPARE_STATEMENT); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + checkOpen("prepareStatement"); throw new SQLException(NOT_SUPPORT_PREPARE_STATEMENT); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + checkOpen("prepareStatement"); throw new SQLException(NOT_SUPPORT_PREPARE_STATEMENT); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + checkOpen("prepareStatement"); throw new SQLException(NOT_SUPPORT_PREPARE_STATEMENT); } @@ -466,27 +535,32 @@ public PreparedStatement prepareStatement(String sql, int resultSetType, int res public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + checkOpen("prepareStatement"); throw new SQLException(NOT_SUPPORT_PREPARE_STATEMENT); } @Override public void releaseSavepoint(Savepoint arg0) throws SQLException { + checkOpen("releaseSavepoint"); throw new SQLException(JdbcMessages.NOT_SUPPORT_RELEASE_SAVEPOINT); } @Override - public void rollback() { + public void rollback() throws SQLException { + checkOpen("rollback"); // do nothing in rollback } @Override - public void rollback(Savepoint arg0) { + public void rollback(Savepoint arg0) throws SQLException { + checkOpen("rollback"); // do nothing in rollback } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { - if (name.equalsIgnoreCase("time_zone")) { + checkOpenForClientInfo("setClientInfo"); + if ("time_zone".equalsIgnoreCase(name)) { try { setTimeZone(value); } catch (TException | IoTDBSQLException e) { @@ -501,14 +575,17 @@ public void setClientInfo(String name, String value) throws SQLClientInfoExcepti @Override public void setNetworkTimeout(Executor arg0, int arg1) throws SQLException { + checkOpen("setNetworkTimeout"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_NETWORK_TIMEOUT); } - public int getQueryTimeout() { + public int getQueryTimeout() throws SQLException { + checkOpen("getQueryTimeout"); return this.queryTimeout; } public void setQueryTimeout(int seconds) throws SQLException { + checkOpen("setQueryTimeout"); if (seconds < 0) { throw new SQLException( String.format(JdbcMessages.QUERY_TIMEOUT_MUST_BE_NON_NEGATIVE, seconds)); @@ -518,11 +595,13 @@ public void setQueryTimeout(int seconds) throws SQLException { @Override public Savepoint setSavepoint() throws SQLException { + checkOpen("setSavepoint"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_SAVEPOINT); } @Override public Savepoint setSavepoint(String arg0) throws SQLException { + checkOpen("setSavepoint"); throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_SAVEPOINT); } @@ -547,19 +626,27 @@ private void openTransport() throws TTransportException { DeepCopyRpcTransportFactory.INSTANCE.getTransport( params.getHost(), params.getPort(), - getNetworkTimeout(), + networkTimeout, params.getTrustStore(), params.getTrustStorePwd()); } else { transport = DeepCopyRpcTransportFactory.INSTANCE.getTransport( - params.getHost(), params.getPort(), getNetworkTimeout()); + params.getHost(), params.getPort(), networkTimeout); } if (!transport.isOpen()) { transport.open(); } } + private void openClient() { + if (params.isRpcThriftCompressionEnabled()) { + setClient(new IClientRPCService.Client(new TCompactProtocol(transport))); + } else { + setClient(new IClientRPCService.Client(new TBinaryProtocol(transport))); + } + } + private void openSession() throws SQLException { TSOpenSessionReq openReq = new TSOpenSessionReq(); @@ -629,17 +716,16 @@ private void openSession() throws SQLException { } public boolean reconnect() { + if (isClosed) { + return false; + } boolean flag = false; for (int i = 1; i <= Config.RETRY_NUM; i++) { try { if (transport != null) { transport.close(); openTransport(); - if (Config.rpcThriftCompressionEnable) { - setClient(new IClientRPCService.Client(new TCompactProtocol(transport))); - } else { - setClient(new IClientRPCService.Client(new TBinaryProtocol(transport))); - } + openClient(); openSession(); setClient(RpcUtils.newSynchronizedClient(getClient())); flag = true; @@ -665,6 +751,8 @@ public String getTimeZone() { } public void setTimeZone(String timeZone) throws TException, IoTDBSQLException { + checkOpenForIoTDBException("setTimeZone"); + ZoneId newZoneId = parseTimeZone(timeZone); TSSetTimeZoneReq req = new TSSetTimeZoneReq(sessionId, timeZone); TSStatus resp = getClient().setTimeZone(req); try { @@ -672,10 +760,22 @@ public void setTimeZone(String timeZone) throws TException, IoTDBSQLException { } 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 { + checkOpenForTException("getServerProperties"); return getClient().getProperties(); } @@ -697,4 +797,54 @@ public int getTimeFactor() { public String getDatabase() { return params.getDb().orElse(null); } + + private void checkOpen(String action) throws SQLException { + if (isClosed) { + throw new SQLException(String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, action)); + } + } + + private void registerStatement(IoTDBStatement statement) { + openStatements.add(statement); + } + + void unregisterStatement(IoTDBStatement statement) { + openStatements.remove(statement); + } + + private SQLException closeOpenStatements() { + SQLException statementCloseException = null; + for (IoTDBStatement statement : new ArrayList<>(openStatements)) { + try { + statement.close(); + } catch (SQLException e) { + if (statementCloseException == null) { + statementCloseException = e; + } else { + statementCloseException.addSuppressed(e); + } + } + } + return statementCloseException; + } + + private void checkOpenForClientInfo(String action) throws SQLClientInfoException { + if (isClosed) { + throw new SQLClientInfoException( + String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, action), null); + } + } + + private void checkOpenForIoTDBException(String action) throws IoTDBSQLException { + if (isClosed) { + throw new IoTDBSQLException( + String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, action)); + } + } + + private void checkOpenForTException(String action) throws TException { + if (isClosed) { + throw new TException(String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, action)); + } + } } diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnectionParams.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnectionParams.java index 1112caabd4e20..1a8c4866bb759 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnectionParams.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBConnectionParams.java @@ -51,6 +51,7 @@ public class IoTDBConnectionParams { private boolean useSSL = false; private String trustStore; private String trustStorePwd; + private boolean rpcThriftCompressionEnabled = Config.rpcThriftCompressionEnable; private String sqlDialect = TREE; @@ -184,6 +185,14 @@ public void setTrustStorePwd(String trustStorePwd) { this.trustStorePwd = trustStorePwd; } + public boolean isRpcThriftCompressionEnabled() { + return rpcThriftCompressionEnabled; + } + + public void setRpcThriftCompressionEnabled(boolean rpcThriftCompressionEnabled) { + this.rpcThriftCompressionEnabled = rpcThriftCompressionEnabled; + } + public String getSqlDialect() { return sqlDialect; } 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 fe31896eb2574..23ccf32fcd046 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 @@ -28,6 +28,7 @@ import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; public class IoTDBDataSource implements DataSource { @@ -37,9 +38,16 @@ public class IoTDBDataSource implements DataSource { private String url; private String user; private String password; - private static final String PWD_STR = "password"; + private String serverName = Config.IOTDB_DEFAULT_HOST; + private String databaseName; + private String dataSourceName; + private String description; + private String networkProtocol; + private String roleName; private Properties properties; - private Integer port = 6667; + private Integer port = Config.IOTDB_DEFAULT_PORT; + private PrintWriter logWriter; + private int loginTimeout; public IoTDBDataSource() { properties = new Properties(); @@ -48,11 +56,9 @@ public IoTDBDataSource() { public IoTDBDataSource(String url, String user, String password, Integer port) { this.url = url; this.properties = new Properties(); - properties.setProperty("user", user); - properties.setProperty(PWD_STR, password); - if (port != 0) { - this.port = port; - } + setUser(user); + setPassword(password); + setPort(port); } public String getUser() { @@ -61,7 +67,7 @@ public String getUser() { public void setUser(String user) { this.user = user; - properties.setProperty("user", user); + setProperty(Config.AUTH_USER, user); } public String getPassword() { @@ -70,7 +76,7 @@ public String getPassword() { public void setPassword(String password) { this.password = password; - properties.setProperty(PWD_STR, password); + setProperty(Config.AUTH_PASSWORD, password); } public Integer getPort() { @@ -78,7 +84,66 @@ public Integer getPort() { } public void setPort(Integer port) { - this.port = 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; + } + + public Integer getPortNumber() { + return getPort(); + } + + public void setPortNumber(Integer portNumber) { + setPort(portNumber); + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getDataSourceName() { + return dataSourceName; + } + + public void setDataSourceName(String dataSourceName) { + this.dataSourceName = dataSourceName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getNetworkProtocol() { + return networkProtocol; + } + + public void setNetworkProtocol(String networkProtocol) { + this.networkProtocol = networkProtocol; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; } public String getUrl() { @@ -89,61 +154,110 @@ public void setUrl(String url) { 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 { - try { - return new IoTDBConnection(url, properties); - } catch (TTransportException e) { - LOGGER.error(JdbcMessages.GET_CONNECTION_ERROR, e); - } - return null; + return createConnection((Properties) properties.clone()); } @Override - public Connection getConnection(String username, String password) { - try { - Properties newProp = new Properties(); - newProp.setProperty("user", username); - newProp.setProperty(PWD_STR, password); - return new IoTDBConnection(url, newProp); - } catch (Exception e) { - LOGGER.error(JdbcMessages.GET_CONNECTION_ERROR, e); - } - return null; + public Connection getConnection(String username, String password) throws SQLException { + Properties newProp = (Properties) properties.clone(); + setProperty(newProp, Config.AUTH_USER, username); + setProperty(newProp, Config.AUTH_PASSWORD, password); + return createConnection(newProp); } @Override public PrintWriter getLogWriter() { - return null; + return logWriter; } @Override public void setLogWriter(PrintWriter printWriter) { - // Do nothing + this.logWriter = printWriter; } @Override - public void setLoginTimeout(int i) { - // Do nothing + public void setLoginTimeout(int i) throws SQLException { + if (i < 0) { + throw new SQLException("loginTimeout must be >= 0"); + } + this.loginTimeout = i; } @Override public int getLoginTimeout() { - return 0; + return loginTimeout; } @Override - public java.util.logging.Logger getParentLogger() { - return null; + public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(JdbcMessages.METHOD_NOT_SUPPORTED); } @Override - public T unwrap(Class aClass) { - return null; + public T unwrap(Class aClass) throws SQLException { + return JdbcWrapperUtils.unwrap(this, aClass); } @Override public boolean isWrapperFor(Class aClass) { - return false; + return JdbcWrapperUtils.isWrapperFor(this, aClass); + } + + private Connection createConnection(Properties connectionProperties) throws SQLException { + applyLoginTimeout(connectionProperties); + try { + return new IoTDBConnection(getConnectionUrl(), connectionProperties); + } catch (TTransportException e) { + LOGGER.error(JdbcMessages.GET_CONNECTION_ERROR, e); + throw new SQLException( + "Connection Error, please check whether the network is available or the server" + + " has started.", + e); + } + } + + String getConnectionUrl() { + if (url != null) { + return url; + } + String host = + serverName == null || serverName.trim().isEmpty() ? Config.IOTDB_DEFAULT_HOST : serverName; + StringBuilder builder = new StringBuilder(Config.IOTDB_URL_PREFIX); + builder.append(host).append(':').append(port); + if (databaseName != null && !databaseName.isEmpty()) { + builder.append('/').append(databaseName); + } + return builder.toString(); + } + + private void applyLoginTimeout(Properties connectionProperties) { + if (loginTimeout <= 0 || connectionProperties.containsKey(Config.NETWORK_TIMEOUT)) { + return; + } + long timeoutInMs = (long) loginTimeout * 1000; + connectionProperties.setProperty( + Config.NETWORK_TIMEOUT, String.valueOf(Math.min(timeoutInMs, Integer.MAX_VALUE))); + } + + private void setProperty(String key, String value) { + setProperty(properties, key, value); + } + + private static void setProperty(Properties properties, String key, String value) { + if (value == null) { + properties.remove(key); + } else { + properties.setProperty(key, value); + } } } 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 2e5994ca426fd..bf2ec57cc9e16 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 @@ -29,6 +29,8 @@ import javax.sql.XADataSource; import java.sql.Driver; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; public class IoTDBDataSourceFactory implements DataSourceFactory { @@ -36,44 +38,161 @@ public class IoTDBDataSourceFactory implements DataSourceFactory { private final Logger logger = LoggerFactory.getLogger(IoTDBDataSourceFactory.class); @Override - public DataSource createDataSource(Properties properties) { + public DataSource createDataSource(Properties properties) throws SQLException { IoTDBDataSource ds = new IoTDBDataSource(); setProperties(ds, properties); return ds; } - public void setProperties(IoTDBDataSource ds, Properties prop) { - Properties properties = (Properties) prop.clone(); - String url = (String) properties.remove(DataSourceFactory.JDBC_URL); + public void setProperties(IoTDBDataSource ds, Properties prop) throws SQLException { + Properties properties = prop == null ? new Properties() : (Properties) prop.clone(); + String url = removeStringProperty(properties, DataSourceFactory.JDBC_URL); if (url != null) { ds.setUrl(url); } - String user = (String) properties.remove(DataSourceFactory.JDBC_USER); - ds.setUser(user); + String user = removeStringProperty(properties, DataSourceFactory.JDBC_USER); + if (user != null) { + ds.setUser(user); + } + + String password = removeStringProperty(properties, DataSourceFactory.JDBC_PASSWORD); + if (password != null) { + ds.setPassword(password); + } + + String serverName = removeStringProperty(properties, DataSourceFactory.JDBC_SERVER_NAME); + if (serverName != null) { + ds.setServerName(serverName); + } + + Integer portNumber = removeIntegerProperty(properties, DataSourceFactory.JDBC_PORT_NUMBER); + if (portNumber != null) { + ds.setPortNumber(portNumber); + } + + String databaseName = removeStringProperty(properties, DataSourceFactory.JDBC_DATABASE_NAME); + if (databaseName != null) { + ds.setDatabaseName(databaseName); + } + + String dataSourceName = + removeStringProperty(properties, DataSourceFactory.JDBC_DATASOURCE_NAME); + if (dataSourceName != null) { + ds.setDataSourceName(dataSourceName); + } + + String description = removeStringProperty(properties, DataSourceFactory.JDBC_DESCRIPTION); + if (description != null) { + ds.setDescription(description); + } + + String networkProtocol = + removeStringProperty(properties, DataSourceFactory.JDBC_NETWORK_PROTOCOL); + if (networkProtocol != null) { + ds.setNetworkProtocol(networkProtocol); + } + + String roleName = removeStringProperty(properties, DataSourceFactory.JDBC_ROLE_NAME); + if (roleName != null) { + ds.setRoleName(roleName); + } - String password = (String) properties.remove(DataSourceFactory.JDBC_PASSWORD); - ds.setPassword(password); + 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); + } } } @Override - public ConnectionPoolDataSource createConnectionPoolDataSource(Properties properties) { - return null; + public ConnectionPoolDataSource createConnectionPoolDataSource(Properties properties) + throws SQLException { + throw new SQLFeatureNotSupportedException(JdbcMessages.METHOD_NOT_SUPPORTED); } @Override - public XADataSource createXADataSource(Properties properties) { - return null; + public XADataSource createXADataSource(Properties properties) throws SQLException { + throw new SQLFeatureNotSupportedException(JdbcMessages.METHOD_NOT_SUPPORTED); } @Override public Driver createDriver(Properties properties) { return new IoTDBDriver(); } + + private static String removeStringProperty(Properties properties, String key) { + Object value = properties.remove(key); + 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); + if (value == null) { + return null; + } + try { + int integerValue = Integer.parseInt(value.toString()); + if (integerValue < 0 || integerValue > 65535) { + throw new NumberFormatException(value.toString()); + } + return integerValue; + } catch (NumberFormatException e) { + throw new SQLException("Invalid JDBC property " + key + ": " + value, e); + } + } + + private static void removeUnsupportedPoolProperties(Properties properties) { + properties.remove(DataSourceFactory.JDBC_INITIAL_POOL_SIZE); + properties.remove(DataSourceFactory.JDBC_MAX_IDLE_TIME); + properties.remove(DataSourceFactory.JDBC_MAX_POOL_SIZE); + properties.remove(DataSourceFactory.JDBC_MAX_STATEMENTS); + properties.remove(DataSourceFactory.JDBC_MIN_POOL_SIZE); + properties.remove(DataSourceFactory.JDBC_PROPERTY_CYCLE); + } } diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadata.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadata.java index 2f4ea5af2401c..add855fb4f8f5 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadata.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadata.java @@ -663,9 +663,12 @@ public String getSchemaTerm() throws SQLException { @Override public String toString() { try { + checkConnectionOpen(); return getMetadataInJsonFunc(); } catch (IoTDBSQLException e) { LOGGER.error(JdbcMessages.FAILED_TO_FETCH_METADATA_JSON, e); + } catch (SQLException e) { + LOGGER.error(JdbcMessages.FAILED_TO_FETCH_METADATA_JSON, e); } catch (TException e) { boolean flag = connection.reconnect(); this.client = connection.getClient(); @@ -694,6 +697,7 @@ public String toString() { * recommend using getMetadataInJson() instead of toString() */ public String getMetadataInJson() throws SQLException { + checkConnectionOpen(); try { return getMetadataInJsonFunc(); } catch (TException e) { diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDriver.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDriver.java index de7065c1c571c..3dcbd4fecb87b 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDriver.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBDriver.java @@ -20,8 +20,10 @@ package org.apache.iotdb.jdbc; import org.apache.iotdb.jdbc.i18n.JdbcMessages; +import org.apache.iotdb.rpc.RpcUtils; import org.apache.thrift.transport.TTransportException; +import org.apache.tsfile.common.conf.TSFileConfig; import org.osgi.service.component.annotations.Component; import java.sql.Connection; @@ -30,6 +32,8 @@ import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.time.ZoneId; +import java.util.Arrays; import java.util.Properties; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -40,7 +44,12 @@ public class IoTDBDriver implements Driver { org.slf4j.LoggerFactory.getLogger(IoTDBDriver.class); /** Is this driver JDBC compliant. */ - private static final boolean TSFILE_JDBC_COMPLIANT = false; + private static final boolean IOTDB_JDBC_COMPLIANT = false; + + private static final String[] BOOLEAN_CHOICES = {"true", "false"}; + private static final String[] SQL_DIALECT_CHOICES = {Constant.TREE, Constant.TABLE}; + private static final String[] VERSION_CHOICES = + Arrays.stream(Constant.Version.values()).map(Enum::name).toArray(String[]::new); static { try { @@ -50,7 +59,7 @@ public class IoTDBDriver implements Driver { } } - private static final String TSFILE_URL_PREFIX = Config.IOTDB_URL_PREFIX + ".*"; + private static final String IOTDB_URL_PATTERN = Config.IOTDB_URL_PREFIX + ".*"; public IoTDBDriver() { // This is a constructor. @@ -58,7 +67,7 @@ public IoTDBDriver() { @Override public boolean acceptsURL(String url) { - return Pattern.matches(TSFILE_URL_PREFIX, url); + return url != null && Pattern.matches(IOTDB_URL_PATTERN, url); } @Override @@ -75,14 +84,12 @@ public Connection connect(String url, Properties info) throws SQLException { @Override public int getMajorVersion() { - // TODO Auto-generated method stub - return 0; + return Config.DRIVER_MAJOR_VERSION; } @Override public int getMinorVersion() { - // TODO Auto-generated method stub - return 0; + return Config.DRIVER_MINOR_VERSION; } @Override @@ -91,13 +98,87 @@ public Logger getParentLogger() throws SQLFeatureNotSupportedException { } @Override - public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { - // TODO Auto-generated method stub - return new DriverPropertyInfo[0]; + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + Properties properties = info == null ? new Properties() : (Properties) info.clone(); + if (url != null && acceptsURL(url)) { + Utils.parseUrl(url, properties); + } + return new DriverPropertyInfo[] { + createProperty( + Config.AUTH_USER, Config.DEFAULT_USER, "User name for authentication.", properties), + createSensitiveProperty(Config.AUTH_PASSWORD, "Password for authentication."), + createProperty( + Config.DEFAULT_BUFFER_CAPACITY, + String.valueOf(RpcUtils.THRIFT_DEFAULT_BUF_CAPACITY), + "Thrift default buffer capacity in bytes.", + properties), + createProperty( + Config.THRIFT_FRAME_MAX_SIZE, + String.valueOf(RpcUtils.THRIFT_FRAME_MAX_SIZE), + "Thrift max frame size in bytes.", + properties), + createProperty( + Config.VERSION, + Config.DEFAULT_VERSION.name(), + VERSION_CHOICES, + "Client compatibility version.", + properties), + createProperty( + Config.NETWORK_TIMEOUT, + String.valueOf(Config.DEFAULT_CONNECTION_TIMEOUT_MS), + "Network timeout in milliseconds.", + properties), + createProperty( + Config.TIME_ZONE, ZoneId.systemDefault().toString(), "Connection time zone.", properties), + createProperty( + Config.CHARSET, TSFileConfig.STRING_CHARSET.name(), "Connection charset.", properties), + createProperty( + Config.USE_SSL, "false", BOOLEAN_CHOICES, "Whether to enable SSL.", properties), + createProperty( + Utils.RPC_COMPRESS, + String.valueOf(Config.rpcThriftCompressionEnable), + BOOLEAN_CHOICES, + "Whether to enable RPC thrift compression.", + properties), + createProperty(Config.TRUST_STORE, null, "SSL trust store path.", properties), + createSensitiveProperty(Config.TRUST_STORE_PWD, "SSL trust store password."), + createProperty( + Config.SQL_DIALECT, + Constant.TREE, + SQL_DIALECT_CHOICES, + "SQL dialect for the connection.", + properties) + }; } @Override public boolean jdbcCompliant() { - return TSFILE_JDBC_COMPLIANT; + return IOTDB_JDBC_COMPLIANT; + } + + private static DriverPropertyInfo createProperty( + String name, String defaultValue, String description, Properties properties) { + return createProperty(name, defaultValue, null, description, properties); + } + + private static DriverPropertyInfo createSensitiveProperty(String name, String description) { + DriverPropertyInfo propertyInfo = new DriverPropertyInfo(name, null); + propertyInfo.required = false; + propertyInfo.description = description; + return propertyInfo; + } + + private static DriverPropertyInfo createProperty( + String name, + String defaultValue, + String[] choices, + String description, + Properties properties) { + DriverPropertyInfo propertyInfo = + new DriverPropertyInfo(name, properties.getProperty(name, defaultValue)); + propertyInfo.required = false; + propertyInfo.choices = choices; + propertyInfo.description = description; + return propertyInfo; } } diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSet.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSet.java index 3ea3c52320d1a..d9f3e94b3f916 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSet.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBJDBCResultSet.java @@ -67,8 +67,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.apache.iotdb.rpc.RpcUtils.convertToTimestamp; - public class IoTDBJDBCResultSet implements ResultSet { public static final String OBJECT_ERR_MSG = "OBJECT Type only support getString"; @@ -85,6 +83,8 @@ public class IoTDBJDBCResultSet implements ResultSet { private List sgColumns = null; private Charset charset = TSFileConfig.STRING_CHARSET; private String timeFormat = RpcUtils.DEFAULT_TIME_FORMAT; + private final long queryId; + private boolean explicitlyClosed = false; @SuppressWarnings("squid:S107") // ignore Methods should not have too many parameters public IoTDBJDBCResultSet( @@ -106,6 +106,7 @@ public IoTDBJDBCResultSet( boolean tableModel, List columnIndex2TsBlockColumnIndexList) throws SQLException { + this.queryId = queryId; this.ioTDBRpcDataSet = new IoTDBRpcDataSet( sql, @@ -116,10 +117,10 @@ public IoTDBJDBCResultSet( moreData, queryId, statement.getStmtId(), - client, + getResultSetClient(client, queryId), sessionId, dataSet, - statement.getFetchSize(), + statement.getFetchSizeInternal(), timeout, zoneId, timeFormat, @@ -152,6 +153,7 @@ public IoTDBJDBCResultSet( boolean moreData, ZoneId zoneId) throws SQLException { + this.queryId = queryId; this.ioTDBRpcDataSet = new IoTDBRpcDataSet( sql, @@ -162,10 +164,10 @@ public IoTDBJDBCResultSet( moreData, queryId, ((IoTDBStatement) statement).getStmtId(), - client, + getResultSetClient(client, queryId), sessionId, dataSet, - statement.getFetchSize(), + ((IoTDBStatement) statement).getFetchSizeInternal(), timeout, zoneId, timeFormat, @@ -180,110 +182,138 @@ public IoTDBJDBCResultSet( } } + private static IClientRPCService.Iface getResultSetClient( + IClientRPCService.Iface client, long queryId) { + return queryId == -1 ? null : client; + } + @Override public boolean isWrapperFor(Class iface) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + checkOpen(); + return JdbcWrapperUtils.isWrapperFor(this, iface); } @Override public T unwrap(Class iface) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + checkOpen(); + return JdbcWrapperUtils.unwrap(this, iface); } @Override public boolean absolute(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void afterLast() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void beforeFirst() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void cancelRowUpdates() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void clearWarnings() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + checkOpen(); + warningChain = null; } @Override public void close() throws SQLException { + if (explicitlyClosed) { + return; + } try { ioTDBRpcDataSet.close(); + statement.clearQueryId(queryId); } catch (StatementExecutionException e) { throw new SQLException(JdbcMessages.CLOSE_SERVER_SIDE_ERROR, e); } catch (TException e) { throw new SQLException(JdbcMessages.CLOSE_CONNECTING_ERROR, e); + } finally { + explicitlyClosed = true; } } @Override public void deleteRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public int findColumn(String columnName) { + public int findColumn(String columnName) throws SQLException { + checkOpen(); + if (!ioTDBRpcDataSet.getColumnNameList().contains(columnName)) { + throw new SQLException("Unknown column name: " + columnName); + } return ioTDBRpcDataSet.findColumn(columnName); } @Override public boolean first() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Array getArray(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Array getArray(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public InputStream getAsciiStream(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public InputStream getAsciiStream(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + checkOpen(); try { return getBigDecimal(ioTDBRpcDataSet.findColumnNameByIndex(columnIndex)); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public BigDecimal getBigDecimal(String columnName) throws SQLException { + checkOpen(); String value = getValueByName(columnName); - if (value != null) { - return new BigDecimal(value); - } else { + if (value == null) { return null; } + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + throw new SQLException(e.getMessage(), e); + } } @Override public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { - MathContext mc = new MathContext(scale); - return getBigDecimal(columnIndex).round(mc); + try { + MathContext mc = new MathContext(scale); + BigDecimal value = getBigDecimal(columnIndex); + return value == null ? null : value.round(mc); + } catch (IllegalArgumentException e) { + throw new SQLException(e.getMessage(), e); + } } @Override @@ -293,16 +323,17 @@ public BigDecimal getBigDecimal(String columnName, int scale) throws SQLExceptio @Override public InputStream getBinaryStream(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public InputStream getBinaryStream(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Blob getBlob(int arg0) throws SQLException { + checkOpen(); try { final TSDataType dataType = ioTDBRpcDataSet.getDataType(arg0); if (dataType == null) { @@ -318,13 +349,14 @@ public Blob getBlob(int arg0) throws SQLException { return new SerialBlob(binary.getValues()); } return null; - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public Blob getBlob(String arg0) throws SQLException { + checkOpen(); try { final TSDataType dataType = ioTDBRpcDataSet.getDataType(arg0); if (dataType == null) { @@ -340,41 +372,44 @@ public Blob getBlob(String arg0) throws SQLException { return new SerialBlob(binary.getValues()); } return null; - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public boolean getBoolean(int columnIndex) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getBoolean(columnIndex); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public boolean getBoolean(String columnName) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getBoolean(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public byte getByte(int columnIndex) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public byte getByte(String columnName) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public byte[] getBytes(int columnIndex) throws SQLException { + checkOpen(); try { final TSDataType dataType = ioTDBRpcDataSet.getDataType(columnIndex); if (dataType == null) { @@ -390,13 +425,14 @@ public byte[] getBytes(int columnIndex) throws SQLException { String s = ioTDBRpcDataSet.getString(columnIndex); return s == null ? null : s.getBytes(charset); } - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public byte[] getBytes(String columnName) throws SQLException { + checkOpen(); try { final TSDataType dataType = ioTDBRpcDataSet.getDataType(columnName); if (dataType == null) { @@ -411,44 +447,46 @@ public byte[] getBytes(String columnName) throws SQLException { String s = ioTDBRpcDataSet.getString(columnName); return s == null ? null : s.getBytes(charset); } - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public Reader getCharacterStream(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Reader getCharacterStream(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Clob getClob(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Clob getClob(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public int getConcurrency() { + public int getConcurrency() throws SQLException { + checkOpen(); return ResultSet.CONCUR_READ_ONLY; } @Override public String getCursorName() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Date getDate(int columnIndex) throws SQLException { - return DateUtils.parseIntToDate(getInt(columnIndex)); + int date = getInt(columnIndex); + return wasNull() ? null : DateUtils.parseIntToDate(date); } @Override @@ -458,119 +496,139 @@ public Date getDate(String columnName) throws SQLException { @Override public Date getDate(int arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Date getDate(String arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public double getDouble(int columnIndex) throws SQLException { + checkOpen(); try { if (TSDataType.FLOAT == ioTDBRpcDataSet.getDataType(columnIndex)) { return ioTDBRpcDataSet.getFloat(columnIndex); } return getDouble(ioTDBRpcDataSet.findColumnNameByIndex(columnIndex)); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public double getDouble(String columnName) throws SQLException { + checkOpen(); try { if (TSDataType.FLOAT == ioTDBRpcDataSet.getDataType(columnName)) { return ioTDBRpcDataSet.getFloat(columnName); } return ioTDBRpcDataSet.getDouble(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override - public int getFetchDirection() { + public int getFetchDirection() throws SQLException { + checkOpen(); return ResultSet.FETCH_FORWARD; } @Override - public void setFetchDirection(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + public void setFetchDirection(int direction) throws SQLException { + checkOpen(); + if (direction != ResultSet.FETCH_FORWARD) { + throw new SQLException(String.format(JdbcMessages.DIRECTION_NOT_SUPPORTED, direction)); + } } @Override public int getFetchSize() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + checkOpen(); + return ioTDBRpcDataSet.getFetchSize(); } @Override - public void setFetchSize(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + public void setFetchSize(int fetchSize) throws SQLException { + checkOpen(); + if (fetchSize < 0) { + throw new SQLException( + String.format(JdbcMessages.FETCH_SIZE_MUST_BE_NON_NEGATIVE, fetchSize)); + } + ioTDBRpcDataSet.setFetchSize(fetchSize == 0 ? Config.DEFAULT_FETCH_SIZE : fetchSize); } @Override public float getFloat(int columnIndex) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getFloat(columnIndex); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public float getFloat(String columnName) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getFloat(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public int getHoldability() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + checkOpen(); + return ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override public int getInt(int columnIndex) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getInt(columnIndex); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public int getInt(String columnName) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getInt(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public long getLong(int columnIndex) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getLong(columnIndex); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public long getLong(String columnName) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getLong(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override - public ResultSetMetaData getMetaData() { + public ResultSetMetaData getMetaData() throws SQLException { + checkOpen(); String operationTypeColumn = ""; boolean nonAlign = false; try { @@ -592,136 +650,141 @@ public ResultSetMetaData getMetaData() { @Override public Reader getNCharacterStream(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Reader getNCharacterStream(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public NClob getNClob(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public NClob getNClob(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public String getNString(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public String getNString(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Object getObject(int columnIndex) throws SQLException { + checkOpen(); try { return getObject(ioTDBRpcDataSet.findColumnNameByIndex(columnIndex)); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public Object getObject(String columnName) throws SQLException { + checkOpen(); return getObjectByName(columnName); } @Override public Object getObject(int arg0, Map> arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Object getObject(String arg0, Map> arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public T getObject(int arg0, Class arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public T getObject(String arg0, Class arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Ref getRef(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Ref getRef(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public int getRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public RowId getRowId(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public RowId getRowId(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public SQLXML getSQLXML(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public SQLXML getSQLXML(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public short getShort(int columnIndex) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public short getShort(String columnName) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public Statement getStatement() { + public Statement getStatement() throws SQLException { + checkOpen(); return this.statement; } @Override public String getString(int columnIndex) throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.getString(columnIndex); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @Override public String getString(String columnName) throws SQLException { + checkOpen(); return getValueByName(columnName); } @Override public Time getTime(int columnIndex) throws SQLException { - long time = statement.getMilliSecond(getLong(columnIndex)); - return new Time(time); + long time = getLong(columnIndex); + return wasNull() ? null : new Time(statement.getMilliSecond(time)); } @Override @@ -731,111 +794,135 @@ public Time getTime(String columnName) throws SQLException { @Override public Time getTime(int arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Time getTime(String arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Timestamp getTimestamp(int columnIndex) throws SQLException { - return convertToTimestamp(getLong(columnIndex), statement.getTimeFactor()); + checkOpen(); + try { + return ioTDBRpcDataSet.getTimestamp(columnIndex); + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { + throw new SQLException(e.getMessage()); + } } @Override public Timestamp getTimestamp(String columnName) throws SQLException { - return new Timestamp(getLong(columnName)); + checkOpen(); + try { + return ioTDBRpcDataSet.getTimestamp(columnName); + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { + throw new SQLException(e.getMessage()); + } } @Override public Timestamp getTimestamp(int arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public Timestamp getTimestamp(String arg0, Calendar arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public int getType() { + public int getType() throws SQLException { + checkOpen(); return ResultSet.TYPE_FORWARD_ONLY; } @Override public URL getURL(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public URL getURL(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public InputStream getUnicodeStream(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public InputStream getUnicodeStream(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public SQLWarning getWarnings() { + public SQLWarning getWarnings() throws SQLException { + checkOpen(); return warningChain; } @Override public void insertRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean isAfterLast() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean isBeforeFirst() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean isClosed() { - return ioTDBRpcDataSet.isClosed(); + return explicitlyClosed; + } + + private void checkOpen() throws SQLException { + if (explicitlyClosed) { + throw new SQLException("ResultSet has been closed"); + } + } + + private SQLException unsupportedOperation() throws SQLException { + checkOpen(); + return new SQLException(Constant.METHOD_NOT_SUPPORTED); } @Override public boolean isFirst() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean isLast() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean last() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void moveToCurrentRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void moveToInsertRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean next() throws SQLException { + checkOpen(); try { return ioTDBRpcDataSet.next(); } catch (StatementExecutionException | IoTDBConnectionException e) { @@ -845,458 +932,459 @@ public boolean next() throws SQLException { @Override public boolean previous() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void refreshRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean relative(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean rowDeleted() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean rowInserted() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public boolean rowUpdated() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateArray(int arg0, Array arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateArray(String arg0, Array arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(int arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(String arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(int arg0, InputStream arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(String arg0, InputStream arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(int arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateAsciiStream(String arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBigDecimal(int arg0, BigDecimal arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBigDecimal(String arg0, BigDecimal arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(int arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(String arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(int arg0, InputStream arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(String arg0, InputStream arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(int arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBinaryStream(String arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(int arg0, Blob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(String arg0, Blob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(int arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(String arg0, InputStream arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(int arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBlob(String arg0, InputStream arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBoolean(int arg0, boolean arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBoolean(String arg0, boolean arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateByte(int arg0, byte arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateByte(String arg0, byte arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBytes(int arg0, byte[] arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateBytes(String arg0, byte[] arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(int arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(String arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(int arg0, Reader arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(String arg0, Reader arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(int arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateCharacterStream(String arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(int arg0, Clob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(String arg0, Clob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(int arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(String arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(int arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateClob(String arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateDate(int arg0, Date arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateDate(String arg0, Date arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateDouble(int arg0, double arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateDouble(String arg0, double arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateFloat(int arg0, float arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateFloat(String arg0, float arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateInt(int arg0, int arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateInt(String arg0, int arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateLong(int arg0, long arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateLong(String arg0, long arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNCharacterStream(int arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNCharacterStream(String arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNCharacterStream(int arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNCharacterStream(String arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(int arg0, NClob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(String arg0, NClob arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(int arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(String arg0, Reader arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(int arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNClob(String arg0, Reader arg1, long arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNString(int arg0, String arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNString(String arg0, String arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNull(int arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateNull(String arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateObject(int arg0, Object arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateObject(String arg0, Object arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateObject(int arg0, Object arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateObject(String arg0, Object arg1, int arg2) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateRef(int arg0, Ref arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateRef(String arg0, Ref arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateRow() throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateRowId(int arg0, RowId arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateRowId(String arg0, RowId arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateSQLXML(int arg0, SQLXML arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateSQLXML(String arg0, SQLXML arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateShort(int arg0, short arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateShort(String arg0, short arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateString(int arg0, String arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateString(String arg0, String arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateTime(int arg0, Time arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateTime(String arg0, Time arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateTimestamp(int arg0, Timestamp arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override public void updateTimestamp(String arg0, Timestamp arg1) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + throw unsupportedOperation(); } @Override - public boolean wasNull() { + public boolean wasNull() throws SQLException { + checkOpen(); return ioTDBRpcDataSet.isLastReadWasNull(); } protected String getValueByName(String columnName) throws SQLException { try { return ioTDBRpcDataSet.getString(columnName); - } catch (StatementExecutionException | IllegalArgumentException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } @@ -1304,51 +1392,65 @@ protected String getValueByName(String columnName) throws SQLException { protected Object getObjectByName(String columnName) throws SQLException { try { return ioTDBRpcDataSet.getObject(columnName); - } catch (StatementExecutionException e) { + } catch (StatementExecutionException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new SQLException(e.getMessage()); } } - public boolean isSetTracingInfo() { + public boolean isSetTracingInfo() throws SQLException { + checkOpen(); if (ioTDBRpcTracingInfo == null) { return false; } return ioTDBRpcTracingInfo.isSetTracingInfo(); } - public List getActivityList() { + public List getActivityList() throws SQLException { + checkOpen(); return ioTDBRpcTracingInfo.getActivityList(); } - public List getElapsedTimeList() { + public List getElapsedTimeList() throws SQLException { + checkOpen(); return ioTDBRpcTracingInfo.getElapsedTimeList(); } public long getStatisticsByName(String name) throws Exception { + checkOpen(); return ioTDBRpcTracingInfo.getStatisticsByName(name); } public String getStatisticsInfoByName(String name) throws Exception { + checkOpen(); return ioTDBRpcTracingInfo.getStatisticsInfoByName(name); } - public boolean isIgnoreTimeStamp() { + public boolean isIgnoreTimeStamp() throws SQLException { + checkOpen(); return ioTDBRpcDataSet.isIgnoreTimeStamp(); } - public String getOperationType() { + public String getOperationType() throws SQLException { + checkOpen(); return this.operationType; } - public List getColumns() { + public List getColumns() throws SQLException { + checkOpen(); return this.columns; } - public List getSgColumns() { + public List getSgColumns() throws SQLException { + checkOpen(); return sgColumns; } - public TSDataType getColumnTypeByIndex(int columnIndex) { - return ioTDBRpcDataSet.getDataType(columnIndex); + public TSDataType getColumnTypeByIndex(int columnIndex) throws SQLException { + checkOpen(); + try { + return ioTDBRpcDataSet.getDataType(columnIndex); + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + throw new SQLException(e.getMessage()); + } } } 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 80f6530c18adf..2f6b85adbdc08 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 @@ -71,6 +71,7 @@ public class IoTDBPreparedStatement extends IoTDBStatement implements PreparedSt private String sql; private static final String METHOD_NOT_SUPPORTED_STRING = JdbcMessages.METHOD_NOT_SUPPORTED; private static final Logger logger = LoggerFactory.getLogger(IoTDBPreparedStatement.class); + private final int parameterCount; /** save the SQL parameters as (paramLoc,paramValue) pairs. */ private final Map parameters = new HashMap<>(); @@ -83,79 +84,113 @@ public class IoTDBPreparedStatement extends IoTDBStatement implements PreparedSt ZoneId zoneId, Charset charset) throws SQLException { + this(connection, client, sessionId, requireNonNullSql(sql), zoneId, charset, true); + } + + private IoTDBPreparedStatement( + IoTDBConnection connection, + Iface client, + Long sessionId, + String sql, + ZoneId zoneId, + Charset charset, + boolean validated) + throws SQLException { super(connection, client, sessionId, zoneId, charset); this.sql = sql; + this.parameterCount = splitSqlStatement(sql).size() - 1; } // Only for tests IoTDBPreparedStatement( IoTDBConnection connection, Iface client, Long sessionId, String sql, ZoneId zoneId) throws SQLException { - super(connection, client, sessionId, zoneId, TSFileConfig.STRING_CHARSET); - this.sql = sql; + this(connection, client, sessionId, sql, zoneId, TSFileConfig.STRING_CHARSET); + } + + private static String requireNonNullSql(String sql) throws SQLException { + if (sql == null) { + throw new SQLException("SQL statement cannot be null"); + } + return sql; } @Override public void addBatch() throws SQLException { + checkConnection("addBatch"); super.addBatch(createCompleteSql(sql, parameters)); } @Override - public void clearParameters() { + public void clearParameters() throws SQLException { + checkConnection("clearParameters"); this.parameters.clear(); } @Override public boolean execute() throws SQLException { + checkConnection("execute"); + closeCurrentResultSet(); return super.execute(createCompleteSql(sql, parameters)); } @Override public ResultSet executeQuery() throws SQLException { + checkConnection("executeQuery"); + closeCurrentResultSet(); return super.executeQuery(createCompleteSql(sql, parameters)); } @Override public int executeUpdate() throws SQLException { + checkConnection("executeUpdate"); + closeCurrentResultSet(); return super.executeUpdate(createCompleteSql(sql, parameters)); } @Override public ResultSetMetaData getMetaData() throws SQLException { - return getResultSet().getMetaData(); + ResultSet currentResultSet = getResultSet(); + return currentResultSet == null ? null : currentResultSet.getMetaData(); } @Override - public ParameterMetaData getParameterMetaData() { + public ParameterMetaData getParameterMetaData() throws SQLException { + checkConnection("getParameterMetaData"); return new ParameterMetaData() { @Override - public int getParameterCount() { - return parameters.size(); + public int getParameterCount() throws SQLException { + checkConnection("getParameterMetaData"); + return parameterCount; } @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,69 +210,116 @@ public int getScale(int param) { } @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 - public T unwrap(Class iface) { - return null; + public T unwrap(Class iface) throws SQLException { + checkConnection("getParameterMetaData"); + return JdbcWrapperUtils.unwrap(this, iface); } @Override - public boolean isWrapperFor(Class iface) { - return false; + public boolean isWrapperFor(Class iface) throws SQLException { + checkConnection("getParameterMetaData"); + return JdbcWrapperUtils.isWrapperFor(this, iface); } }; } + private void checkParameterMetadataIndex(int param) throws SQLException { + checkConnection("getParameterMetaData"); + checkParameterIndexRange(param); + } + + private void checkParameterIndex(int param) throws SQLException { + checkConnection("set parameter"); + checkParameterIndexRange(param); + } + + private SQLException unsupportedParameterOperation(int param, String message) + throws SQLException { + checkConnection("set parameter"); + return new SQLException(message); + } + + private void checkParameterIndexRange(int param) throws SQLException { + if (param < 1 || param > parameterCount) { + throw new SQLException( + "Parameter index out of range: " + param + " (expected 1-" + parameterCount + ")"); + } + } + + private void setParameter(int parameterIndex, String value) throws SQLException { + checkParameterIndex(parameterIndex); + this.parameters.put(parameterIndex, value); + } + + 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); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + checkParameterIndex(parameterIndex); + 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); @@ -245,156 +327,170 @@ public void setBinaryStream(int parameterIndex, InputStream x, int length) throw for (byte b : bytes) { sb.append(String.format("%02x", b)); } - this.parameters.put(parameterIndex, "X'" + sb.toString() + "'"); + setParameter(parameterIndex, "X'" + sb.toString() + "'"); } catch (IOException e) { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } } @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override - public void setBoolean(int parameterIndex, boolean x) { - this.parameters.put(parameterIndex, Boolean.toString(x)); + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + setParameter(parameterIndex, Boolean.toString(x)); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { + if (x == null) { + setNull(parameterIndex, Types.BINARY); + return; + } + checkParameterIndex(parameterIndex); Binary binary = new Binary(x); - this.parameters.put(parameterIndex, binary.getStringValue(TSFileConfig.STRING_CHARSET)); + setParameter(parameterIndex, binary.getStringValue(TSFileConfig.STRING_CHARSET)); } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setDate(int parameterIndex, Date x) throws SQLException { + if (x == null) { + setNull(parameterIndex, Types.DATE); + return; + } + checkParameterIndex(parameterIndex); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); - this.parameters.put(parameterIndex, "'" + dateFormat.format(x) + "'"); + setParameter(parameterIndex, "'" + dateFormat.format(x) + "'"); } @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override - public void setDouble(int parameterIndex, double x) { - this.parameters.put(parameterIndex, Double.toString(x)); + public void setDouble(int parameterIndex, double x) throws SQLException { + setParameter(parameterIndex, Double.toString(x)); } @Override - public void setFloat(int parameterIndex, float x) { - this.parameters.put(parameterIndex, Float.toString(x)); + public void setFloat(int parameterIndex, float x) throws SQLException { + setParameter(parameterIndex, Float.toString(x)); } @Override - public void setInt(int parameterIndex, int x) { - this.parameters.put(parameterIndex, Integer.toString(x)); + public void setInt(int parameterIndex, int x) throws SQLException { + setParameter(parameterIndex, Integer.toString(x)); } @Override - public void setLong(int parameterIndex, long x) { - this.parameters.put(parameterIndex, Long.toString(x)); + public void setLong(int parameterIndex, long x) throws SQLException { + setParameter(parameterIndex, Long.toString(x)); } @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNString(int parameterIndex, String value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { - this.parameters.put(parameterIndex, "NULL"); + setParameter(parameterIndex, "NULL"); } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + checkConnection("set parameter"); throw new SQLException(Constant.PARAMETER_NOT_NULL); } @Override public void setObject(int parameterIndex, Object x) throws SQLException { - if (x instanceof String) { + checkConnection("set parameter"); + 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); @@ -441,6 +537,7 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ @Override public void setObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException { + checkConnection("set parameter"); if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { @@ -512,7 +609,7 @@ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType case Types.VARBINARY: case Types.LONGVARBINARY: case Types.BLOB: - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); case Types.DATE: case Types.TIMESTAMP: java.util.Date parameterAsDate; @@ -566,14 +663,14 @@ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType break; case Types.OTHER: - throw new SQLException(Constant.PARAMETER_SUPPORTED); // + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); // default: - throw new SQLException(Constant.PARAMETER_SUPPORTED); // + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); // } } catch (SQLException ex) { throw ex; } catch (Exception ex) { - throw new SQLException(Constant.PARAMETER_SUPPORTED); // + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); // } } } @@ -890,30 +987,30 @@ private void setNumericObject( @Override public void setRef(int parameterIndex, Ref x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + throw unsupportedParameterOperation(parameterIndex, METHOD_NOT_SUPPORTED_STRING); } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + throw unsupportedParameterOperation(parameterIndex, METHOD_NOT_SUPPORTED_STRING); } @Override public void setShort(int parameterIndex, short x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override - public void setString(int parameterIndex, String x) { + public void setString(int parameterIndex, String x) throws SQLException { if (x == null) { - this.parameters.put(parameterIndex, null); + setParameter(parameterIndex, null); } else { - this.parameters.put(parameterIndex, "'" + escapeSingleQuotes(x) + "'"); + setParameter(parameterIndex, "'" + escapeSingleQuotes(x) + "'"); } } @@ -924,6 +1021,11 @@ private String escapeSingleQuotes(String value) { @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(); String timeprecision = client.getProperties().getTimestampPrecision(); @@ -948,6 +1050,11 @@ public void setTime(int parameterIndex, Time x) throws SQLException { @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + if (x == null) { + setNull(parameterIndex, Types.TIME); + return; + } + checkParameterIndex(parameterIndex); try { ZonedDateTime zonedDateTime = null; long time = x.getTime(); @@ -971,8 +1078,7 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio } else { zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), super.zoneId); } - this.parameters.put( - parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + setParameter(parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } catch (TException e) { logger.error( String.format("set time error when iotdb prepared statement :%s ", e.getMessage())); @@ -980,15 +1086,24 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio } @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; + } + checkParameterIndex(parameterIndex); ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(x.getTime()), super.zoneId); - this.parameters.put( - parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + setParameter(parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + if (x == null) { + setNull(parameterIndex, Types.TIMESTAMP); + return; + } + checkParameterIndex(parameterIndex); ZonedDateTime zonedDateTime = null; if (cal != null) { zonedDateTime = @@ -997,18 +1112,17 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S } else { zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(x.getTime()), super.zoneId); } - this.parameters.put( - parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + setParameter(parameterIndex, zonedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } @Override public void setURL(int parameterIndex, URL x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } private String createCompleteSql(final String sql, Map parameters) @@ -1024,7 +1138,8 @@ private String createCompleteSql(final String sql, Map paramete 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/IoTDBResultMetadata.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBResultMetadata.java index 59279d88465c3..973f6a888a4de 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBResultMetadata.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBResultMetadata.java @@ -70,12 +70,12 @@ public IoTDBResultMetadata( @Override public boolean isWrapperFor(Class arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + return JdbcWrapperUtils.isWrapperFor(this, arg0); } @Override public T unwrap(Class arg0) throws SQLException { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); + return JdbcWrapperUtils.unwrap(this, arg0); } @SuppressWarnings({ @@ -83,6 +83,7 @@ public T unwrap(Class arg0) throws SQLException { }) // ignore Cognitive Complexity of methods should not be too high @Override public String getCatalogName(int column) throws SQLException { + checkColumnIndex(column); String systemSchmea = "_system_schmea"; String system = "_system"; String systemUser = "_system_user"; @@ -92,9 +93,6 @@ public String getCatalogName(int column) throws SQLException { String systemNull = ""; String columnName = columnInfoList.get(column - 1); List listColumns = columnInfoList; - if (column < 1 || column > columnInfoList.size()) { - throw new SQLException(Constant.METHOD_NOT_SUPPORTED); - } if ("SHOW".equals(operationType)) { if ("count".equals(listColumns.get(0))) { return systemDatabase; @@ -135,16 +133,22 @@ public String getCatalogName(int column) throws SQLException { } else if (!"FILL".equals(operationType)) { return systemNull; } - if (nonAlign) { - return sgColumns.get(column - 1); - } else { - return sgColumns.get(column - 2); + if (sgColumns == null || sgColumns.isEmpty()) { + return systemNull; } + int sgColumnIndex = nonAlign ? column - 1 : column - 2; + if (sgColumnIndex < 0 || sgColumnIndex >= sgColumns.size()) { + return systemNull; + } + return sgColumns.get(sgColumnIndex); } @Override public String getColumnClassName(int column) throws SQLException { String columnTypeName = getColumnTypeName(column); + if (columnTypeName == null) { + return null; + } switch (columnTypeName) { case TIMESTAMP: return Timestamp.class.getName(); @@ -210,7 +214,7 @@ public int getColumnType(int column) throws SQLException { if (column == 1 && !ignoreTimestamp) { return Types.TIMESTAMP; } - String columnType = columnTypeList.get(column - 1); + String columnType = getColumnTypeString(column); switch (columnType.toUpperCase()) { case BOOLEAN: @@ -244,7 +248,7 @@ public String getColumnTypeName(int column) throws SQLException { if (column == 1 && !ignoreTimestamp) { return TIMESTAMP; } - String columnType = columnTypeList.get(column - 1); + String columnType = getColumnTypeString(column); String typeString = columnType.toUpperCase(); if (BOOLEAN.equals(typeString) || INT32.equals(typeString) @@ -267,7 +271,7 @@ public int getPrecision(int column) throws SQLException { if (column == 1 && !ignoreTimestamp) { return 3; } - String columnType = columnTypeList.get(column - 1); + String columnType = getColumnTypeString(column); switch (columnType.toUpperCase()) { case BOOLEAN: return 1; @@ -299,7 +303,7 @@ public int getScale(int column) throws SQLException { if (column == 1 && !ignoreTimestamp) { return 0; } - String columnType = columnTypeList.get(column - 1); + String columnType = getColumnTypeString(column); switch (columnType.toUpperCase()) { case BOOLEAN: case INT32: @@ -337,47 +341,67 @@ public String getTableName(int column) throws SQLException { } @Override - public boolean isAutoIncrement(int arg0) throws SQLException { + public boolean isAutoIncrement(int column) throws SQLException { + checkColumnIndex(column); return false; } @Override - public boolean isCaseSensitive(int arg0) throws SQLException { + public boolean isCaseSensitive(int column) throws SQLException { + checkColumnIndex(column); return true; } @Override - public boolean isCurrency(int arg0) throws SQLException { + public boolean isCurrency(int column) throws SQLException { + checkColumnIndex(column); return false; } @Override - public boolean isDefinitelyWritable(int arg0) throws SQLException { + public boolean isDefinitelyWritable(int column) throws SQLException { + checkColumnIndex(column); return false; } @Override - public int isNullable(int arg0) throws SQLException { + public int isNullable(int column) throws SQLException { + checkColumnIndex(column); return 1; } @Override - public boolean isReadOnly(int arg0) throws SQLException { + public boolean isReadOnly(int column) throws SQLException { + checkColumnIndex(column); return true; } @Override - public boolean isSearchable(int arg0) throws SQLException { + public boolean isSearchable(int column) throws SQLException { + checkColumnIndex(column); return true; } @Override - public boolean isSigned(int arg0) throws SQLException { + public boolean isSigned(int column) throws SQLException { + checkColumnIndex(column); return true; } @Override - public boolean isWritable(int arg0) throws SQLException { + public boolean isWritable(int column) throws SQLException { + checkColumnIndex(column); return false; } + + private String getColumnTypeString(int column) throws SQLException { + if (columnTypeList == null || column > columnTypeList.size()) { + throw new SQLException(String.format(JdbcMessages.COLUMN_DOES_NOT_EXIST, column)); + } + String columnType = columnTypeList.get(column - 1); + if (columnType == null) { + throw new SQLException(String.format(JdbcMessages.COLUMN_DOES_NOT_EXIST, column)); + } + return columnType; + } } 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 540ae847b5473..a75a54862faaa 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 IoTDBSQLException(String reason) { 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/IoTDBStatement.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBStatement.java index 1c6ce8c0e9b14..d2818d60b5812 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBStatement.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBStatement.java @@ -73,6 +73,8 @@ public class IoTDBStatement implements Statement { private List batchSQLList; private static final String NOT_SUPPORT_EXECUTE = "Not support execute"; private static final String NOT_SUPPORT_EXECUTE_UPDATE = "Not support executeUpdate"; + private static final String CANNOT_AFTER_STATEMENT_CLOSED = + "Cannot %s after statement has been closed!"; /** Keep state so we can fail certain calls made after close(). */ private boolean isClosed = false; @@ -181,17 +183,20 @@ public class IoTDBStatement implements Statement { } @Override - public boolean isWrapperFor(Class iface) { - return false; + public boolean isWrapperFor(Class iface) throws SQLException { + checkConnection("isWrapperFor"); + return JdbcWrapperUtils.isWrapperFor(this, iface); } @Override public T unwrap(Class iface) throws SQLException { - throw new SQLException(JdbcMessages.CANNOT_UNWRAP_TO + iface); + checkConnection("unwrap"); + return JdbcWrapperUtils.unwrap(this, iface); } @Override - public void addBatch(String sql) { + public void addBatch(String sql) throws SQLException { + checkConnection("addBatch"); if (batchSQLList == null) { batchSQLList = new ArrayList<>(); } @@ -217,7 +222,12 @@ public void cancel() throws SQLException { } @Override - public void clearBatch() { + public void clearBatch() throws SQLException { + checkConnection("clearBatch"); + clearBatchInternal(); + } + + private void clearBatchInternal() { if (batchSQLList == null) { batchSQLList = new ArrayList<>(); } @@ -225,11 +235,12 @@ public void clearBatch() { } @Override - public void clearWarnings() { + public void clearWarnings() throws SQLException { + checkConnection("clearWarnings"); warningChain = null; } - private void closeClientOperation() throws SQLException { + protected void closeClientOperation() throws SQLException { try { if (stmtId != -1) { TSCloseOperationReq closeReq = new TSCloseOperationReq(sessionId); @@ -243,19 +254,70 @@ private void closeClientOperation() throws SQLException { } } + protected void closeQueryOperation(long queryIdToClose) throws SQLException { + try { + if (queryIdToClose != -1) { + TSCloseOperationReq closeReq = new TSCloseOperationReq(sessionId); + closeReq.setStatementId(stmtId); + closeReq.setQueryId(queryIdToClose); + TSStatus closeResp = client.closeOperation(closeReq); + RpcUtils.verifySuccess(closeResp); + if (queryId == queryIdToClose) { + queryId = -1; + } + } + } catch (Exception e) { + throw new SQLException(JdbcMessages.CLOSE_STATEMENT_ERROR, e); + } + } + + protected void clearQueryId(long closedQueryId) { + if (queryId == closedQueryId) { + queryId = -1; + } + } + + protected void setQueryId(long queryId) { + this.queryId = queryId; + } + @Override public void close() throws SQLException { - if (isClosed) { + if (isClosed()) { + unregisterStatement(); return; } - closeClientOperation(); - isClosed = true; + SQLException closeException = null; + try { + closeCurrentResultSet(); + } catch (SQLException e) { + closeException = mergeSQLException(closeException, e); + } + + boolean clientOperationClosed = false; + try { + closeClientOperation(); + clientOperationClosed = true; + } catch (SQLException e) { + closeException = mergeSQLException(closeException, e); + } + + if (clientOperationClosed) { + isClosed = true; + } + if (isClosed) { + unregisterStatement(); + } + + if (closeException != null) { + throw closeException; + } } @Override public void closeOnCompletion() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_CLOSE_ON_COMPLETION); + throw unsupportedOperation("closeOnCompletion", JdbcMessages.NOT_SUPPORT_CLOSE_ON_COMPLETION); } /** @@ -273,7 +335,7 @@ public boolean execute(byte[] sql) throws SQLException { @Override public boolean execute(String sql) throws SQLException { checkConnection("execute"); - isClosed = false; + closeCurrentResultSet(); try { return executeSQL(sql); } catch (TException e) { @@ -286,17 +348,17 @@ public boolean execute(String sql) throws SQLException { @Override public boolean execute(String arg0, int arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE); + throw unsupportedOperation("execute", NOT_SUPPORT_EXECUTE); } @Override public boolean execute(String arg0, int[] arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE); + throw unsupportedOperation("execute", NOT_SUPPORT_EXECUTE); } @Override public boolean execute(String arg0, String[] arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE); + throw unsupportedOperation("execute", NOT_SUPPORT_EXECUTE); } private interface TFunction { @@ -383,46 +445,55 @@ private boolean executeSQL(String sql) throws TException, SQLException { } if (execResp.isSetColumns()) { - queryId = execResp.getQueryId(); + queryId = execResp.isSetQueryId() ? execResp.getQueryId() : -1; if (execResp.queryResult == null) { - throw new SQLException(JdbcMessages.QUERY_RESULT_SHOULD_NOT_BE_NULL); + SQLException exception = new SQLException(JdbcMessages.QUERY_RESULT_SHOULD_NOT_BE_NULL); + throw closeQueryOperationOnResultSetCreationFailure(queryId, exception); } else { - this.resultSet = - new IoTDBJDBCResultSet( - this, - execResp.getColumns(), - execResp.getDataTypeList(), - execResp.columnNameIndexMap, - execResp.isIgnoreTimeStamp(), - client, - sql, - queryId, - sessionId, - execResp.queryResult, - execResp.tracingInfo, - execReq.timeout, - execResp.moreData, - zoneId, - charset, - execResp.isSetTableModel() && execResp.isTableModel(), - execResp.getColumnIndex2TsBlockColumnIndexList()); + try { + this.resultSet = + new IoTDBJDBCResultSet( + this, + execResp.getColumns(), + execResp.getDataTypeList(), + execResp.columnNameIndexMap, + execResp.isIgnoreTimeStamp(), + client, + sql, + queryId, + sessionId, + execResp.queryResult, + execResp.tracingInfo, + execReq.timeout, + execResp.moreData, + zoneId, + charset, + execResp.isSetTableModel() && execResp.isTableModel(), + execResp.getColumnIndex2TsBlockColumnIndexList()); + } catch (SQLException | RuntimeException e) { + throw closeQueryOperationOnResultSetCreationFailure(queryId, e); + } } return true; } + if (execResp.isSetQueryId()) { + queryId = execResp.getQueryId(); + closeQueryOperation(queryId); + } return false; } @Override public int[] executeBatch() throws SQLException { checkConnection("executeBatch"); - isClosed = false; + closeCurrentResultSet(); try { return executeBatchSQL(); } catch (TException e) { throw new SQLException( "Fail to reconnect to server when executing batch sqls. please check server status", e); } finally { - clearBatch(); + clearBatchInternal(); } } @@ -478,7 +549,7 @@ public ResultSet executeQuery(String sql) throws SQLException { public ResultSet executeQuery(String sql, long timeoutInMS) throws SQLException { checkConnection("execute query"); - isClosed = false; + closeCurrentResultSet(); try { return executeQuerySQL(sql, timeoutInMS); } catch (TException e) { @@ -501,7 +572,7 @@ private ResultSet executeQuerySQL(String sql, long timeoutInMS) throws TExceptio TSExecuteStatementResp execResp = callWithRetryAndReconnect( () -> client.executeQueryStatementV2(execReq), TSExecuteStatementResp::getStatus); - queryId = execResp.getQueryId(); + queryId = execResp.isSetQueryId() ? execResp.getQueryId() : -1; try { RpcUtils.verifySuccess(execResp.getStatus()); } catch (StatementExecutionException e) { @@ -509,27 +580,32 @@ private ResultSet executeQuerySQL(String sql, long timeoutInMS) throws TExceptio } if (!execResp.isSetQueryResult()) { - throw new SQLException(JdbcMessages.QUERY_RESULT_SHOULD_NOT_BE_NULL); + SQLException exception = new SQLException(JdbcMessages.QUERY_RESULT_SHOULD_NOT_BE_NULL); + throw closeQueryOperationOnResultSetCreationFailure(queryId, exception); } else { - this.resultSet = - new IoTDBJDBCResultSet( - this, - execResp.getColumns(), - execResp.getDataTypeList(), - execResp.columnNameIndexMap, - execResp.isIgnoreTimeStamp(), - client, - sql, - queryId, - sessionId, - execResp.getQueryResult(), - execResp.tracingInfo, - execReq.timeout, - execResp.moreData, - zoneId, - charset, - execResp.isSetTableModel() && execResp.isTableModel(), - execResp.getColumnIndex2TsBlockColumnIndexList()); + try { + this.resultSet = + new IoTDBJDBCResultSet( + this, + execResp.getColumns(), + execResp.getDataTypeList(), + execResp.columnNameIndexMap, + execResp.isIgnoreTimeStamp(), + client, + sql, + queryId, + sessionId, + execResp.getQueryResult(), + execResp.tracingInfo, + execReq.timeout, + execResp.moreData, + zoneId, + charset, + execResp.isSetTableModel() && execResp.isTableModel(), + execResp.getColumnIndex2TsBlockColumnIndexList()); + } catch (SQLException | RuntimeException e) { + throw closeQueryOperationOnResultSetCreationFailure(queryId, e); + } } return resultSet; } @@ -545,7 +621,7 @@ private BitSet listToBitSet(List listAlias) { @Override public int executeUpdate(String sql) throws SQLException { checkConnection("execute update"); - isClosed = false; + closeCurrentResultSet(); try { return executeUpdateSQL(sql); } catch (TException e) { @@ -557,17 +633,17 @@ public int executeUpdate(String sql) throws SQLException { @Override public int executeUpdate(String arg0, int arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE_UPDATE); + throw unsupportedOperation("executeUpdate", NOT_SUPPORT_EXECUTE_UPDATE); } @Override public int executeUpdate(String arg0, int[] arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE_UPDATE); + throw unsupportedOperation("executeUpdate", NOT_SUPPORT_EXECUTE_UPDATE); } @Override public int executeUpdate(String arg0, String[] arg1) throws SQLException { - throw new SQLException(NOT_SUPPORT_EXECUTE_UPDATE); + throw unsupportedOperation("executeUpdate", NOT_SUPPORT_EXECUTE_UPDATE); } private int executeUpdateSQL(final String sql) @@ -576,19 +652,21 @@ private int executeUpdateSQL(final String sql) final TSExecuteStatementResp execResp = callWithRetryAndReconnect( () -> client.executeUpdateStatement(execReq), TSExecuteStatementResp::getStatus); - if (execResp.isSetQueryId()) { - queryId = execResp.getQueryId(); - } try { RpcUtils.verifySuccess(execResp.getStatus()); } catch (final StatementExecutionException e) { throw new IoTDBSQLException(e.getMessage(), execResp.getStatus()); } + if (execResp.isSetQueryId()) { + queryId = execResp.getQueryId(); + closeQueryOperation(queryId); + } return 0; } @Override - public Connection getConnection() { + public Connection getConnection() throws SQLException { + checkConnection("getConnection"); return connection; } @@ -612,6 +690,10 @@ public int getFetchSize() throws SQLException { return fetchSize; } + int getFetchSizeInternal() { + return fetchSize; + } + @Override public void setFetchSize(int fetchSize) throws SQLException { checkConnection("setFetchSize"); @@ -624,21 +706,22 @@ public void setFetchSize(int fetchSize) throws SQLException { @Override public ResultSet getGeneratedKeys() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_GENERATED_KEYS); + throw unsupportedOperation("getGeneratedKeys", JdbcMessages.NOT_SUPPORT_GET_GENERATED_KEYS); } @Override public int getMaxFieldSize() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_MAX_FIELD_SIZE); + throw unsupportedOperation("getMaxFieldSize", JdbcMessages.NOT_SUPPORT_GET_MAX_FIELD_SIZE); } @Override public void setMaxFieldSize(int arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_MAX_FIELD_SIZE); + throw unsupportedOperation("setMaxFieldSize", JdbcMessages.NOT_SUPPORT_GET_MAX_FIELD_SIZE); } @Override public int getMaxRows() throws SQLException { + checkConnection("getMaxRows"); return this.maxRows; } @@ -653,22 +736,29 @@ public void setMaxRows(int num) throws SQLException { @Override public boolean getMoreResults() throws SQLException { + checkConnection("getMoreResults"); + closeCurrentResultSet(); return false; } @Override public boolean getMoreResults(int arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_MORE_RESULTS); + throw unsupportedOperation("getMoreResults", JdbcMessages.NOT_SUPPORT_GET_MORE_RESULTS); } @Override - public int getQueryTimeout() { + public int getQueryTimeout() throws SQLException { + checkConnection("getQueryTimeout"); return this.queryTimeout; } @Override public void setQueryTimeout(int seconds) throws SQLException { checkConnection("setQueryTimeout"); + if (seconds < 0) { + throw new SQLException( + String.format(JdbcMessages.QUERY_TIMEOUT_MUST_BE_NON_NEGATIVE, seconds)); + } this.queryTimeout = seconds; } @@ -680,12 +770,14 @@ public ResultSet getResultSet() throws SQLException { @Override public int getResultSetConcurrency() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_RESULT_SET_CONCURRENCY); + throw unsupportedOperation( + "getResultSetConcurrency", JdbcMessages.NOT_SUPPORT_GET_RESULT_SET_CONCURRENCY); } @Override public int getResultSetHoldability() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_GET_RESULT_SET_HOLDABILITY); + checkConnection("getResultSetHoldability"); + return ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override @@ -695,49 +787,97 @@ public int getResultSetType() throws SQLException { } @Override - public int getUpdateCount() { + public int getUpdateCount() throws SQLException { + checkConnection("getUpdateCount"); return -1; } @Override - public SQLWarning getWarnings() { + public SQLWarning getWarnings() throws SQLException { + checkConnection("getWarnings"); return warningChain; } @Override public boolean isCloseOnCompletion() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_IS_CLOSE_ON_COMPLETION); + throw unsupportedOperation( + "isCloseOnCompletion", JdbcMessages.NOT_SUPPORT_IS_CLOSE_ON_COMPLETION); } @Override public boolean isClosed() { - return isClosed; + return isClosed || connection == null || connection.isClosed(); } @Override public boolean isPoolable() throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_IS_POOLABLE); + throw unsupportedOperation("isPoolable", JdbcMessages.NOT_SUPPORT_IS_POOLABLE); } @Override public void setPoolable(boolean arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_POOLABLE); + throw unsupportedOperation("setPoolable", JdbcMessages.NOT_SUPPORT_SET_POOLABLE); } @Override public void setCursorName(String arg0) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_CURSOR_NAME); + throw unsupportedOperation("setCursorName", JdbcMessages.NOT_SUPPORT_SET_CURSOR_NAME); } @Override public void setEscapeProcessing(boolean enable) throws SQLException { - throw new SQLException(JdbcMessages.NOT_SUPPORT_SET_ESCAPE_PROCESSING); + throw unsupportedOperation( + "setEscapeProcessing", JdbcMessages.NOT_SUPPORT_SET_ESCAPE_PROCESSING); } - private void checkConnection(String action) throws SQLException { + protected void checkConnection(String action) throws SQLException { if (connection == null || connection.isClosed()) { throw new SQLException(String.format(JdbcMessages.CANNOT_AFTER_CONNECTION_CLOSED, action)); } + if (isClosed) { + throw new SQLException(String.format(CANNOT_AFTER_STATEMENT_CLOSED, action)); + } + } + + protected void closeCurrentResultSet() throws SQLException { + if (resultSet != null) { + ResultSet currentResultSet = resultSet; + resultSet = null; + currentResultSet.close(); + } + } + + protected static SQLException mergeSQLException(SQLException current, SQLException next) { + if (current == null) { + return next; + } + current.addSuppressed(next); + return current; + } + + protected SQLException closeQueryOperationOnResultSetCreationFailure( + long queryIdToClose, Exception failure) { + SQLException resultSetCreationException = + failure instanceof SQLException + ? (SQLException) failure + : new SQLException(failure.getMessage(), failure); + try { + closeQueryOperation(queryIdToClose); + } catch (SQLException closeException) { + resultSetCreationException.addSuppressed(closeException); + } + return resultSetCreationException; + } + + private void unregisterStatement() { + if (connection != null) { + connection.unregisterStatement(this); + } + } + + private SQLException unsupportedOperation(String action, String message) throws SQLException { + checkConnection(action); + return new SQLException(message); } private boolean reInit() throws SQLException { 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 674ec164691a9..59a88fbedc6d0 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 @@ -91,6 +91,18 @@ public class IoTDBTablePreparedStatement extends IoTDBStatement implements Prepa ZoneId zoneId, Charset charset) throws SQLException { + this(connection, client, sessionId, requireNonNullSql(sql), zoneId, charset, true); + } + + private IoTDBTablePreparedStatement( + IoTDBConnection connection, + Iface client, + Long sessionId, + String sql, + ZoneId zoneId, + Charset charset, + boolean validated) + throws SQLException { super(connection, client, sessionId, zoneId, charset); this.sql = sql; this.preparedStatementName = generateStatementName(); @@ -115,12 +127,19 @@ public class IoTDBTablePreparedStatement extends IoTDBStatement implements Prepa parameterTypes[i] = Types.NULL; } } catch (TException | StatementExecutionException e) { - throw new SQLException(JdbcMessages.FAILED_TO_PREPARE_STATEMENT + e.getMessage(), e); + SQLException prepareException = + new SQLException(JdbcMessages.FAILED_TO_PREPARE_STATEMENT + e.getMessage(), e); + try { + closeClientOperation(); + } catch (SQLException closeException) { + prepareException.addSuppressed(closeException); + } + throw prepareException; } } else { // For non-query statements, only keep text parameters for client-side substitution. this.serverSidePrepared = false; - this.parameterCount = 0; + this.parameterCount = splitSqlStatement(sql).size() - 1; this.parameterValues = null; this.parameterTypes = null; } @@ -133,6 +152,13 @@ public class IoTDBTablePreparedStatement extends IoTDBStatement implements Prepa this(connection, client, sessionId, sql, zoneId, TSFileConfig.STRING_CHARSET); } + private static String requireNonNullSql(String sql) throws SQLException { + if (sql == null) { + throw new SQLException("SQL statement cannot be null"); + } + return sql; + } + private String generateStatementName() { // StatementId is unique in directly connected DataNode return "jdbc_ps_" + getStmtId(); @@ -140,11 +166,13 @@ private String generateStatementName() { @Override public void addBatch() throws SQLException { + checkConnection("addBatch"); super.addBatch(createCompleteSql(sql, parameters)); } @Override - public void clearParameters() { + public void clearParameters() throws SQLException { + checkConnection("clearParameters"); this.parameters.clear(); if (serverSidePrepared) { for (int i = 0; i < parameterCount; i++) { @@ -156,10 +184,11 @@ public void clearParameters() { @Override public boolean execute() throws SQLException { + checkConnection("execute"); if (isQueryStatement(sql)) { - TSExecuteStatementResp resp = executeInternal(); - return resp.isSetQueryDataSet() || resp.isSetQueryResult(); + return processQueryResult(executeInternal()) != null; } else { + closeCurrentResultSet(); return super.execute(createCompleteSql(sql, parameters)); } } @@ -174,16 +203,20 @@ private boolean isQueryStatement(String sql) { @Override public ResultSet executeQuery() throws SQLException { + checkConnection("executeQuery"); TSExecuteStatementResp resp = executeInternal(); return processQueryResult(resp); } @Override public int executeUpdate() throws SQLException { + checkConnection("executeUpdate"); + closeCurrentResultSet(); return super.executeUpdate(createCompleteSql(sql, parameters)); } private TSExecuteStatementResp executeInternal() throws SQLException { + closeCurrentResultSet(); // Validate all parameters are set for (int i = 0; i < parameterCount; i++) { if (parameterTypes[i] == Types.NULL @@ -213,31 +246,51 @@ private TSExecuteStatementResp executeInternal() throws SQLException { } private ResultSet processQueryResult(TSExecuteStatementResp resp) throws SQLException { + long queryId = resp.isSetQueryId() ? resp.getQueryId() : -1; if (resp.isSetQueryDataSet() || resp.isSetQueryResult()) { - this.resultSet = - new IoTDBJDBCResultSet( - this, - resp.getColumns(), - resp.getDataTypeList(), - resp.columnNameIndexMap, - resp.ignoreTimeStamp, - client, - sql, - resp.queryId, - sessionId, - resp.queryResult, - resp.tracingInfo, - (long) queryTimeout * 1000, - resp.isSetMoreData() && resp.isMoreData(), - zoneId); + setQueryId(queryId); + if (resp.queryResult == null) { + SQLException exception = new SQLException(JdbcMessages.QUERY_RESULT_SHOULD_NOT_BE_NULL); + throw closeQueryOperationOnResultSetCreationFailure(queryId, exception); + } + try { + this.resultSet = + new IoTDBJDBCResultSet( + this, + resp.getColumns(), + resp.getDataTypeList(), + resp.columnNameIndexMap, + resp.ignoreTimeStamp, + client, + sql, + queryId, + sessionId, + resp.queryResult, + resp.tracingInfo, + (long) queryTimeout * 1000, + resp.isSetMoreData() && resp.isMoreData(), + zoneId); + } catch (SQLException | RuntimeException e) { + throw closeQueryOperationOnResultSetCreationFailure(queryId, e); + } return resultSet; } + if (queryId != -1) { + setQueryId(queryId); + closeQueryOperation(queryId); + } return null; } @Override public void close() throws SQLException { + SQLException closeException = null; if (!isClosed() && serverSidePrepared) { + try { + closeCurrentResultSet(); + } catch (SQLException e) { + closeException = mergeSQLException(closeException, e); + } // Deallocate prepared statement on server only if it was prepared server-side TSDeallocatePreparedReq req = new TSDeallocatePreparedReq(); req.setSessionId(sessionId); @@ -252,11 +305,19 @@ public void close() throws SQLException { logger.warn(JdbcMessages.ERROR_DEALLOCATING_PREPARED_STATEMENT, e); } } - super.close(); + try { + super.close(); + } catch (SQLException e) { + closeException = mergeSQLException(closeException, e); + } + if (closeException != null) { + throw closeException; + } } @Override public ResultSetMetaData getMetaData() throws SQLException { + checkConnection("getMetaData"); if (resultSet != null) { return resultSet.getMetaData(); } @@ -264,20 +325,24 @@ public ResultSetMetaData getMetaData() throws SQLException { } @Override - public ParameterMetaData getParameterMetaData() { + public ParameterMetaData getParameterMetaData() throws SQLException { + checkConnection("getParameterMetaData"); return new ParameterMetaData() { @Override - public int getParameterCount() { + public int getParameterCount() throws SQLException { + checkConnection("getParameterMetaData"); return parameterCount; } @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 +354,20 @@ public boolean isSigned(int param) { } @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,28 +375,33 @@ public int getParameterType(int param) { } @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; } @Override - public T unwrap(Class iface) { - return null; + public T unwrap(Class iface) throws SQLException { + checkConnection("getParameterMetaData"); + return JdbcWrapperUtils.unwrap(this, iface); } @Override - public boolean isWrapperFor(Class iface) { - return false; + public boolean isWrapperFor(Class iface) throws SQLException { + checkConnection("getParameterMetaData"); + return JdbcWrapperUtils.isWrapperFor(this, iface); } }; } @@ -393,6 +466,10 @@ public void setString(int parameterIndex, String x) throws SQLException { @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 +478,10 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException { @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 +496,10 @@ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLExceptio @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 +530,10 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio @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(); @@ -459,6 +548,7 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S @Override public void setObject(int parameterIndex, Object x) throws SQLException { + checkConnection("set parameter"); if (x == null) { setNull(parameterIndex, Types.NULL); } else if (x instanceof String) { @@ -501,9 +591,22 @@ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType } private void checkParameterIndex(int index) throws SQLException { - if (!serverSidePrepared) { - return; - } + checkConnection("set parameter"); + checkParameterIndexRange(index); + } + + private SQLException unsupportedParameterOperation(int index, String message) + throws SQLException { + checkConnection("set parameter"); + return new SQLException(message); + } + + private void checkParameterMetadataIndex(int index) throws SQLException { + checkConnection("getParameterMetaData"); + checkParameterIndexRange(index); + } + + private void checkParameterIndexRange(int index) throws SQLException { if (index < 1 || index > parameterCount) { throw new SQLException( "Parameter index out of range: " + index + " (expected 1-" + parameterCount + ")"); @@ -524,36 +627,44 @@ private String escapeSingleQuotes(String value) { @Override public void setArray(int parameterIndex, Array x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + checkParameterIndex(parameterIndex); + 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); @@ -564,106 +675,106 @@ public void setBinaryStream(int parameterIndex, InputStream x, int length) throw @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setNString(int parameterIndex, String value) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + throw unsupportedParameterOperation(parameterIndex, METHOD_NOT_SUPPORTED_STRING); } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { - throw new SQLException(METHOD_NOT_SUPPORTED_STRING); + throw unsupportedParameterOperation(parameterIndex, METHOD_NOT_SUPPORTED_STRING); } @Override @@ -673,12 +784,12 @@ public void setShort(int parameterIndex, short x) throws SQLException { @Override public void setURL(int parameterIndex, URL x) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new SQLException(Constant.PARAMETER_SUPPORTED); + throw unsupportedParameterOperation(parameterIndex, Constant.PARAMETER_SUPPORTED); } // ================== Helper Methods for Backward Compatibility ================== @@ -692,7 +803,8 @@ private String createCompleteSql(final String sql, Map paramete 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/IoTDBURLException.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBURLException.java index 81b12077e8cc8..a704218eb46c1 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBURLException.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBURLException.java @@ -28,4 +28,8 @@ public class IoTDBURLException extends SQLException { public IoTDBURLException(String reason) { super(reason); } + + public IoTDBURLException(String reason, Throwable cause) { + super(reason, cause); + } } diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/JdbcWrapperUtils.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/JdbcWrapperUtils.java new file mode 100644 index 0000000000000..8f9148d0e1ef3 --- /dev/null +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/JdbcWrapperUtils.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package org.apache.iotdb.jdbc; + +import org.apache.iotdb.jdbc.i18n.JdbcMessages; + +import java.sql.SQLException; + +final class JdbcWrapperUtils { + + static boolean isWrapperFor(Object wrapper, Class iface) { + return iface != null && iface.isInstance(wrapper); + } + + static T unwrap(Object wrapper, Class iface) throws SQLException { + if (isWrapperFor(wrapper, iface)) { + return iface.cast(wrapper); + } + throw new SQLException(JdbcMessages.CANNOT_UNWRAP_TO + iface); + } + + private JdbcWrapperUtils() {} +} 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 00e46cc340d15..5957421d9499e 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 @@ -26,7 +26,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -/** Utils to convert between thrift format and TsFile format. */ +/** Utilities for JDBC URL parsing. */ public class Utils { @SuppressWarnings({ @@ -46,51 +46,53 @@ public class Utils { * jdbc:iotdb://localhost:6667/. */ static IoTDBConnectionParams parseUrl(String url, Properties info) throws IoTDBURLException { + Properties properties = info == null ? new Properties() : info; IoTDBConnectionParams params = new IoTDBConnectionParams(url); - if (url.trim().equalsIgnoreCase(Config.IOTDB_URL_PREFIX)) { - return params; - } - boolean isUrlLegal = false; + boolean isUrlLegal = url.trim().equalsIgnoreCase(Config.IOTDB_URL_PREFIX); Matcher matcher = null; String host = null; String suffixURL = null; - if (url.startsWith(Config.IOTDB_URL_PREFIX)) { + if (!isUrlLegal && url.startsWith(Config.IOTDB_URL_PREFIX)) { String subURL = url.substring(Config.IOTDB_URL_PREFIX.length()); - int i = subURL.lastIndexOf(COLON); - host = subURL.substring(0, i); + int authorityEnd = findAuthorityEnd(subURL); + String authority = subURL.substring(0, authorityEnd); + int portSeparatorIndex = authority.lastIndexOf(COLON); + if (portSeparatorIndex <= 0 || portSeparatorIndex == authority.length() - 1) { + throw new IoTDBURLException( + "Error url format, url should be jdbc:iotdb://anything:port/[database] or jdbc:iotdb://anything:port[/database]?property1=value1&property2=value2, current url is " + + url); + } + host = authority.substring(0, portSeparatorIndex); params.setHost(host); - i++; + String portText = authority.substring(portSeparatorIndex + 1); // parse port - int port = 0; - for (; i < subURL.length() && Character.isDigit(subURL.charAt(i)); i++) { - port = port * 10 + (subURL.charAt(i) - '0'); - } - suffixURL = i < subURL.length() ? subURL.substring(i) : ""; + int port = parsePort(portText); + suffixURL = subURL.substring(authorityEnd); // legal port if (port >= 1 && port <= 65535) { params.setPort(port); // parse database - if (i < subURL.length() && subURL.charAt(i) == SLASH) { - int endIndex = subURL.indexOf(PARAMETER_SEPARATOR, i + 1); + if (!suffixURL.isEmpty() && suffixURL.charAt(0) == SLASH) { + int endIndex = suffixURL.indexOf(PARAMETER_SEPARATOR, 1); String database; - if (endIndex <= i + 1) { - if (i + 1 == subURL.length()) { + if (endIndex < 0) { + if (suffixURL.length() == 1) { database = null; } else { - database = subURL.substring(i + 1); + database = suffixURL.substring(1); } suffixURL = ""; } else { - database = subURL.substring(i + 1, endIndex); - suffixURL = subURL.substring(endIndex); + database = endIndex == 1 ? null : suffixURL.substring(1, endIndex); + suffixURL = suffixURL.substring(endIndex); } params.setDb(database); } matcher = SUFFIX_URL_PATTERN.matcher(suffixURL); - if (matcher.matches() && parseUrlParam(subURL, info)) { + if (matcher.matches() && parseUrlParam(subURL, properties)) { isUrlLegal = true; } } @@ -101,43 +103,46 @@ static IoTDBConnectionParams parseUrl(String url, Properties info) throws IoTDBU + url); } - if (info.containsKey(Config.AUTH_USER)) { - params.setUsername(info.getProperty(Config.AUTH_USER)); + if (properties.containsKey(Config.AUTH_USER)) { + params.setUsername(properties.getProperty(Config.AUTH_USER)); } - if (info.containsKey(Config.AUTH_PASSWORD)) { - params.setPassword(info.getProperty(Config.AUTH_PASSWORD)); + if (properties.containsKey(Config.AUTH_PASSWORD)) { + params.setPassword(properties.getProperty(Config.AUTH_PASSWORD)); } - if (info.containsKey(Config.DEFAULT_BUFFER_CAPACITY)) { + if (properties.containsKey(Config.DEFAULT_BUFFER_CAPACITY)) { params.setThriftDefaultBufferSize( - Integer.parseInt(info.getProperty(Config.DEFAULT_BUFFER_CAPACITY))); + parsePositiveIntegerProperty(properties, Config.DEFAULT_BUFFER_CAPACITY)); } - if (info.containsKey(Config.THRIFT_FRAME_MAX_SIZE)) { + if (properties.containsKey(Config.THRIFT_FRAME_MAX_SIZE)) { params.setThriftMaxFrameSize( - Integer.parseInt(info.getProperty(Config.THRIFT_FRAME_MAX_SIZE))); + parsePositiveIntegerProperty(properties, Config.THRIFT_FRAME_MAX_SIZE)); } - if (info.containsKey(Config.VERSION)) { - params.setVersion(Constant.Version.valueOf(info.getProperty(Config.VERSION))); + if (properties.containsKey(Config.VERSION)) { + params.setVersion(parseVersionProperty(properties)); } - if (info.containsKey(Config.NETWORK_TIMEOUT)) { - params.setNetworkTimeout(Integer.parseInt(info.getProperty(Config.NETWORK_TIMEOUT))); + if (properties.containsKey(Config.NETWORK_TIMEOUT)) { + params.setNetworkTimeout(parseNonNegativeIntegerProperty(properties, Config.NETWORK_TIMEOUT)); } - if (info.containsKey(Config.TIME_ZONE)) { - params.setTimeZone(info.getProperty(Config.TIME_ZONE)); + if (properties.containsKey(Config.TIME_ZONE)) { + params.setTimeZone(validateTimeZoneProperty(properties)); } - if (info.containsKey(Config.CHARSET)) { - params.setCharset(info.getProperty(Config.CHARSET)); + if (properties.containsKey(Config.CHARSET)) { + params.setCharset(validateCharsetProperty(properties)); } - if (info.containsKey(Config.USE_SSL)) { - params.setUseSSL(Boolean.parseBoolean(info.getProperty(Config.USE_SSL))); + if (properties.containsKey(Config.USE_SSL)) { + params.setUseSSL(parseBooleanProperty(properties, Config.USE_SSL)); } - if (info.containsKey(Config.TRUST_STORE)) { - params.setTrustStore(info.getProperty(Config.TRUST_STORE)); + if (properties.containsKey(Config.TRUST_STORE)) { + params.setTrustStore(properties.getProperty(Config.TRUST_STORE)); } - if (info.containsKey(Config.TRUST_STORE_PWD)) { - params.setTrustStorePwd(info.getProperty(Config.TRUST_STORE_PWD)); + if (properties.containsKey(Config.TRUST_STORE_PWD)) { + params.setTrustStorePwd(properties.getProperty(Config.TRUST_STORE_PWD)); } - if (info.containsKey(Config.SQL_DIALECT)) { - params.setSqlDialect(info.getProperty(Config.SQL_DIALECT)); + if (properties.containsKey(RPC_COMPRESS)) { + params.setRpcThriftCompressionEnabled(parseBooleanProperty(properties, RPC_COMPRESS)); + } + if (properties.containsKey(Config.SQL_DIALECT)) { + params.setSqlDialect(validateSqlDialectProperty(properties)); } return params; @@ -156,28 +161,69 @@ private static boolean parseUrlParam(String subURL, Properties info) { return true; } String paramURL = subURL.substring(subURL.indexOf('?') + 1); - String[] params = paramURL.split("&"); + String[] params = paramURL.split("&", -1); for (String tmpParam : params) { - String[] paramSplit = tmpParam.split("="); - if (paramSplit.length != 2) { + int separatorIndex = tmpParam.indexOf('='); + if (separatorIndex <= 0 || separatorIndex == tmpParam.length() - 1) { return false; } - String key = tmpParam.split("=")[0]; - String value = tmpParam.split("=")[1]; + 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 ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { - Config.rpcThriftCompressionEnable = Boolean.parseBoolean(value); + if (isBoolean(value)) { + info.put(key, value); } else { return false; } break; - case Config.USE_SSL: case Config.TRUST_STORE: case Config.TRUST_STORE_PWD: + info.put(key, value); + break; + case Config.USE_SSL: + if (!isBoolean(value)) { + return false; + } + info.put(key, value); + break; case Config.VERSION: + try { + Constant.Version.valueOf(value); + } catch (IllegalArgumentException e) { + return false; + } + info.put(key, value); + break; case Config.NETWORK_TIMEOUT: + try { + if (Integer.parseInt(value) < 0) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + info.put(key, value); + break; case Config.SQL_DIALECT: + if (!Constant.TREE.equals(value) && !Constant.TABLE.equals(value)) { + return false; + } info.put(key, value); break; case Config.TIME_ZONE: @@ -204,5 +250,126 @@ private static boolean parseUrlParam(String subURL, Properties info) { return true; } + private static boolean isBoolean(String value) { + return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); + } + + private static int findAuthorityEnd(String subURL) { + int slashIndex = subURL.indexOf(SLASH); + int parameterIndex = subURL.indexOf(PARAMETER_SEPARATOR); + if (slashIndex < 0) { + return parameterIndex < 0 ? subURL.length() : parameterIndex; + } + if (parameterIndex < 0) { + return slashIndex; + } + return Math.min(slashIndex, parameterIndex); + } + + private static int parsePort(String portText) { + if (portText.isEmpty()) { + return -1; + } + for (int i = 0; i < portText.length(); i++) { + if (!Character.isDigit(portText.charAt(i))) { + return -1; + } + } + try { + return Integer.parseInt(portText); + } catch (NumberFormatException e) { + return -1; + } + } + + private static int parseIntegerProperty(Properties properties, String key) + throws IoTDBURLException { + String value = getPropertyValue(properties, key); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw invalidPropertyValue(key, value, e); + } + } + + private static int parsePositiveIntegerProperty(Properties properties, String key) + throws IoTDBURLException { + int value = parseIntegerProperty(properties, key); + if (value <= 0) { + throw invalidPropertyValue(key, String.valueOf(value), null); + } + return value; + } + + private static int parseNonNegativeIntegerProperty(Properties properties, String key) + throws IoTDBURLException { + int value = parseIntegerProperty(properties, key); + if (value < 0) { + throw invalidPropertyValue(key, String.valueOf(value), null); + } + return value; + } + + private static Constant.Version parseVersionProperty(Properties properties) + throws IoTDBURLException { + String value = getPropertyValue(properties, Config.VERSION); + try { + return Constant.Version.valueOf(value); + } catch (IllegalArgumentException e) { + throw invalidPropertyValue(Config.VERSION, value, e); + } + } + + private static boolean parseBooleanProperty(Properties properties, String key) + throws IoTDBURLException { + String value = getPropertyValue(properties, key); + if (!isBoolean(value)) { + throw invalidPropertyValue(key, value, null); + } + return Boolean.parseBoolean(value); + } + + private static String validateTimeZoneProperty(Properties properties) throws IoTDBURLException { + String value = getPropertyValue(properties, Config.TIME_ZONE); + try { + ZoneId.of(value); + } catch (DateTimeException e) { + throw invalidPropertyValue(Config.TIME_ZONE, value, e); + } + return value; + } + + private static String validateCharsetProperty(Properties properties) throws IoTDBURLException { + String value = getPropertyValue(properties, Config.CHARSET); + try { + Charset.forName(value); + } catch (Exception e) { + throw invalidPropertyValue(Config.CHARSET, value, e); + } + return value; + } + + private static String validateSqlDialectProperty(Properties properties) throws IoTDBURLException { + String value = getPropertyValue(properties, Config.SQL_DIALECT); + if (!Constant.TREE.equals(value) && !Constant.TABLE.equals(value)) { + throw invalidPropertyValue(Config.SQL_DIALECT, value, null); + } + return value; + } + + private static String getPropertyValue(Properties properties, String key) + throws IoTDBURLException { + String value = properties.getProperty(key); + if (value == null) { + throw invalidPropertyValue(key, null, null); + } + return value; + } + + private static IoTDBURLException invalidPropertyValue(String key, String value, Throwable cause) { + return new IoTDBURLException( + "Invalid value for JDBC connection property " + key + ": " + value, cause); + } + private Utils() {} } diff --git a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/relational/IoTDBRelationalDatabaseMetadata.java b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/relational/IoTDBRelationalDatabaseMetadata.java index 7ddfca1c01a9d..eec5d1d29bdca 100644 --- a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/relational/IoTDBRelationalDatabaseMetadata.java +++ b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/relational/IoTDBRelationalDatabaseMetadata.java @@ -225,10 +225,9 @@ public ResultSet getTables( legacyMode = false; } catch (SQLException e1) { LOGGER.error(SHOW_TABLES_ERROR_MSG, e.getMessage()); + close(null, stmt); throw e; } - } finally { - stmt.close(); } // Setup Fields @@ -257,43 +256,43 @@ public ResultSet getTables( columnNameIndex.put(fields[i].getName(), i); } - // Extract Values boolean hasResultSet = false; - while (rs.next()) { - hasResultSet = true; - List valueInRow = new ArrayList<>(); - for (int i = 0; i < fields.length; i++) { - if (i == 0) { - valueInRow.add(schemaPattern); - } else if (i == 1) { - // valueInRow.add(rs.getString(2)); - valueInRow.add(legacyMode ? rs.getString("table_name") : rs.getString("TableName")); - } else if (i == 2) { - valueInRow.add("TABLE"); - } else if (i == 3) { - // String tgtString = ""; - // String ttl = rs.getString("ttl(ms)"); - // tgtString += "TTL(ms): " + ttl; - String comment = legacyMode ? rs.getString("comment") : rs.getString("Comment"); - if (comment != null && !comment.isEmpty()) { - valueInRow.add(comment); + ByteBuffer tsBlock = null; + try { + // Extract Values + while (rs.next()) { + hasResultSet = true; + List valueInRow = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + if (i == 0) { + valueInRow.add(schemaPattern); + } else if (i == 1) { + // valueInRow.add(rs.getString(2)); + valueInRow.add(legacyMode ? rs.getString("table_name") : rs.getString("TableName")); + } else if (i == 2) { + valueInRow.add("TABLE"); + } else if (i == 3) { + // String tgtString = ""; + // String ttl = rs.getString("ttl(ms)"); + // tgtString += "TTL(ms): " + ttl; + String comment = legacyMode ? rs.getString("comment") : rs.getString("Comment"); + if (comment != null && !comment.isEmpty()) { + valueInRow.add(comment); + } else { + valueInRow.add(""); + } + } else if (i == 4) { + valueInRow.add(getTypePrecision(fields[i].getSqlType())); + } else if (i == 5) { + valueInRow.add(getTypeScale(fields[i].getSqlType())); } else { - valueInRow.add(""); + valueInRow.add("TABLE"); } - } else if (i == 4) { - valueInRow.add(getTypePrecision(fields[i].getSqlType())); - } else if (i == 5) { - valueInRow.add(getTypeScale(fields[i].getSqlType())); - } else { - valueInRow.add("TABLE"); } + valuesList.add(valueInRow); } - valuesList.add(valueInRow); - } - // Convert Values to ByteBuffer - ByteBuffer tsBlock = null; - try { + // Convert Values to ByteBuffer tsBlock = convertTsBlock(valuesList, tsDataTypeList); } catch (IOException e) { LOGGER.error(CONVERT_ERROR_MSG, e.getMessage()); @@ -358,11 +357,9 @@ public ResultSet getColumns( legacyMode = false; } catch (SQLException e1) { LOGGER.error(SHOW_TABLES_ERROR_MSG, e.getMessage()); + close(null, stmt); throw e; } - - } finally { - stmt.close(); } // Setup Fields @@ -428,85 +425,85 @@ public ResultSet getColumns( columnNameIndex.put(fields[i].getName(), i); } - // Extract Metadata int count = 1; - while (rs.next()) { - String columnName = - legacyMode ? rs.getString("column_name") : rs.getString("ColumnName"); // 3 - String type = legacyMode ? rs.getString("datatype") : rs.getString("DataType"); // 4 - List valueInRow = new ArrayList<>(); - for (int i = 0; i < fields.length; i++) { - if (i == 0) { - valueInRow.add(""); - } else if (i == 1) { - valueInRow.add(schemaPattern); - } else if (i == 2) { - valueInRow.add(tableNamePattern); - } else if (i == 3) { - valueInRow.add(columnName); - } else if (i == 4) { - valueInRow.add(getSQLType(type)); - } else if (i == 5) { - valueInRow.add(type); - } else if (i == 6) { - valueInRow.add(0); - } else if (i == 7) { - valueInRow.add(65535); - } else if (i == 8) { - valueInRow.add(getTypeScale(fields[i].getSqlType())); - } else if (i == 9) { - valueInRow.add(0); - } else if (i == 10) { - if (!columnName.equals("time")) { - valueInRow.add(ResultSetMetaData.columnNullableUnknown); - } else { - valueInRow.add(ResultSetMetaData.columnNoNulls); - } - } else if (i == 11) { - String comment = legacyMode ? rs.getString("comment") : rs.getString("Comment"); - if (comment != null && !comment.isEmpty()) { - valueInRow.add(comment); - } else { + ByteBuffer tsBlock = null; + try { + // Extract Metadata + while (rs.next()) { + String columnName = + legacyMode ? rs.getString("column_name") : rs.getString("ColumnName"); // 3 + String type = legacyMode ? rs.getString("datatype") : rs.getString("DataType"); // 4 + List valueInRow = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + if (i == 0) { + valueInRow.add(""); + } else if (i == 1) { + valueInRow.add(schemaPattern); + } else if (i == 2) { + valueInRow.add(tableNamePattern); + } else if (i == 3) { + valueInRow.add(columnName); + } else if (i == 4) { + valueInRow.add(getSQLType(type)); + } else if (i == 5) { + valueInRow.add(type); + } else if (i == 6) { + valueInRow.add(0); + } else if (i == 7) { + valueInRow.add(65535); + } else if (i == 8) { + valueInRow.add(getTypeScale(fields[i].getSqlType())); + } else if (i == 9) { + valueInRow.add(0); + } else if (i == 10) { + if (!columnName.equals("time")) { + valueInRow.add(ResultSetMetaData.columnNullableUnknown); + } else { + valueInRow.add(ResultSetMetaData.columnNoNulls); + } + } else if (i == 11) { + String comment = legacyMode ? rs.getString("comment") : rs.getString("Comment"); + if (comment != null && !comment.isEmpty()) { + valueInRow.add(comment); + } else { + valueInRow.add(""); + } + } else if (i == 12) { + valueInRow.add(""); + } else if (i == 13) { + valueInRow.add(0); + } else if (i == 14) { + valueInRow.add(0); + } else if (i == 15) { + valueInRow.add(65535); + } else if (i == 16) { + valueInRow.add(count++); + } else if (i == 17) { + if (!columnName.equals("time")) { + valueInRow.add("YES"); + } else { + valueInRow.add("NO"); + } + } else if (i == 18) { + valueInRow.add(""); + } else if (i == 19) { + valueInRow.add(""); + } else if (i == 20) { + valueInRow.add(""); + } else if (i == 21) { + valueInRow.add(0); + } else if (i == 22) { + valueInRow.add(""); + } else if (i == 23) { valueInRow.add(""); - } - } else if (i == 12) { - valueInRow.add(""); - } else if (i == 13) { - valueInRow.add(0); - } else if (i == 14) { - valueInRow.add(0); - } else if (i == 15) { - valueInRow.add(65535); - } else if (i == 16) { - valueInRow.add(count++); - } else if (i == 17) { - if (!columnName.equals("time")) { - valueInRow.add("YES"); } else { - valueInRow.add("NO"); + valueInRow.add(""); } - } else if (i == 18) { - valueInRow.add(""); - } else if (i == 19) { - valueInRow.add(""); - } else if (i == 20) { - valueInRow.add(""); - } else if (i == 21) { - valueInRow.add(0); - } else if (i == 22) { - valueInRow.add(""); - } else if (i == 23) { - valueInRow.add(""); - } else { - valueInRow.add(""); } + valuesList.add(valueInRow); } - valuesList.add(valueInRow); - } - // Convert Values to ByteBuffer - ByteBuffer tsBlock = null; - try { + // Convert Values to ByteBuffer tsBlock = convertTsBlock(valuesList, tsDataTypeList); } catch (IOException e) { LOGGER.error(CONVERT_ERROR_MSG, e.getMessage()); @@ -556,11 +553,9 @@ public ResultSet getPrimaryKeys(String catalog, String schemaPattern, String tab legacyMode = false; } catch (SQLException e1) { LOGGER.error(SHOW_TABLES_ERROR_MSG, e.getMessage()); + close(null, stmt); throw e; } - - } finally { - stmt.close(); } Field[] fields = new Field[6]; @@ -589,37 +584,37 @@ public ResultSet getPrimaryKeys(String catalog, String schemaPattern, String tab } int count = 1; - while (rs.next()) { - String columnName = legacyMode ? rs.getString("column_name") : rs.getString("ColumnName"); - String category = legacyMode ? rs.getString("category") : rs.getString("Category"); - if (category.equals("TAG") || category.equals("TIME")) { - List valueInRow = new ArrayList<>(); - for (int i = 0; i < fields.length; ++i) { - if (i == 0) { - valueInRow.add(schemaPattern); - } else if (i == 1) { - valueInRow.add(schemaPattern); - } else if (i == 2) { - valueInRow.add(tableNamePattern); - } else if (i == 3) { - valueInRow.add(columnName); - } else if (i == 4) { - valueInRow.add(count++); - } else { - valueInRow.add(PRIMARY); + ByteBuffer tsBlock = null; + try { + while (rs.next()) { + String columnName = legacyMode ? rs.getString("column_name") : rs.getString("ColumnName"); + String category = legacyMode ? rs.getString("category") : rs.getString("Category"); + if (category.equals("TAG") || category.equals("TIME")) { + List valueInRow = new ArrayList<>(); + for (int i = 0; i < fields.length; ++i) { + if (i == 0) { + valueInRow.add(schemaPattern); + } else if (i == 1) { + valueInRow.add(schemaPattern); + } else if (i == 2) { + valueInRow.add(tableNamePattern); + } else if (i == 3) { + valueInRow.add(columnName); + } else if (i == 4) { + valueInRow.add(count++); + } else { + valueInRow.add(PRIMARY); + } } + valuesList.add(valueInRow); } - valuesList.add(valueInRow); } - } - ByteBuffer tsBlock = null; - try { tsBlock = convertTsBlock(valuesList, tsDataTypeList); } catch (IOException e) { LOGGER.error(JdbcMessages.RELATIONAL_GET_PRIMARY_KEYS_ERROR, e.getMessage()); } finally { - close(null, stmt); + close(rs, stmt); } return new IoTDBJDBCResultSet( 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 2919e51d265a5..078b5432f465d 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 @@ -23,10 +23,14 @@ import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.service.rpc.thrift.IClientRPCService; import org.apache.iotdb.service.rpc.thrift.ServerProperties; +import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq; +import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp; import org.apache.iotdb.service.rpc.thrift.TSGetTimeZoneResp; +import org.apache.iotdb.service.rpc.thrift.TSPrepareReq; import org.apache.iotdb.service.rpc.thrift.TSSetTimeZoneReq; import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -34,15 +38,31 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLClientInfoException; import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; import java.time.ZoneId; import java.util.ArrayList; import java.util.List; +import java.util.Properties; 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.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class IoTDBConnectionTest { @@ -63,6 +83,7 @@ public void tearDown() {} @Test public void testSetTimeZone() throws IoTDBSQLException, TException { + openConnection(connection); String timeZone = "Asia/Shanghai"; when(client.setTimeZone(any(TSSetTimeZoneReq.class))).thenReturn(new TSStatus(successStatus)); connection.setClient(client); @@ -81,6 +102,7 @@ public void testGetTimeZone() throws TException { @Test public void testSetTimeZoneByClientInfo() throws TException, SQLClientInfoException { + openConnection(connection); String timeZone = "+07:00"; assertNotEquals(connection.getTimeZone(), timeZone); when(client.setTimeZone(any(TSSetTimeZoneReq.class))).thenReturn(new TSStatus(successStatus)); @@ -89,8 +111,104 @@ public void testSetTimeZoneByClientInfo() throws TException, SQLClientInfoExcept assertEquals(connection.getTimeZone(), timeZone); } + @Test + public void testSetTimeZoneRejectsInvalidZoneBeforeRpc() throws TException { + openConnection(connection); + connection.setClient(client); + + assertThrows(IoTDBSQLException.class, () -> connection.setTimeZone("invalid-zone")); + verify(client, never()).setTimeZone(any(TSSetTimeZoneReq.class)); + } + + @Test + public void testSetClientInfoWrapsInvalidTimeZone() { + openConnection(connection); + SQLClientInfoException exception = + assertThrows( + SQLClientInfoException.class, + () -> connection.setClientInfo("time_zone", "invalid-zone")); + + assertTrue(exception.getCause() instanceof IoTDBSQLException); + } + + @Test + public void testSetClientInfoRejectsNullNameWithoutNpe() { + openConnection(connection); + assertThrows(SQLClientInfoException.class, () -> connection.setClientInfo(null, "value")); + } + + @Test + public void testTableCatalogAndSchemaRejectNull() { + IoTDBConnection tableConnection = + new IoTDBConnection() { + @Override + public String getSqlDialect() { + return Constant.TABLE_DIALECT; + } + }; + openConnection(tableConnection); + + assertThrows(SQLException.class, () -> tableConnection.setCatalog(null)); + assertThrows(SQLException.class, () -> tableConnection.setSchema(null)); + } + + @Test + public void testTableCatalogAndSchemaCloseUseStatements() throws Exception { + IoTDBConnection tableConnection = + new IoTDBConnection() { + @Override + public String getSqlDialect() { + return Constant.TABLE_DIALECT; + } + }; + openConnection(tableConnection); + tableConnection.setClient(client); + TSExecuteStatementResp resp = mock(TSExecuteStatementResp.class); + when(client.requestStatementId(anyLong())).thenReturn(1L, 2L); + when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(resp); + when(resp.getStatus()).thenReturn(successStatus); + when(client.closeOperation(any())).thenReturn(successStatus); + + tableConnection.setSchema("root"); + tableConnection.setCatalog("root2"); + + verify(client, times(2)).closeOperation(any()); + } + + @Test + public void testPrepareStatementRejectsNullSqlBeforeRequestingStatementId() throws Exception { + openConnection(connection); + connection.setClient(client); + + assertThrows(SQLException.class, () -> connection.prepareStatement(null)); + + verify(client, never()).requestStatementId(anyLong()); + verify(client, never()).closeOperation(any()); + } + + @Test + public void testTablePrepareStatementRejectsNullSqlBeforeRequestingStatementId() + throws Exception { + IoTDBConnection tableConnection = + new IoTDBConnection() { + @Override + public String getSqlDialect() { + return Constant.TABLE_DIALECT; + } + }; + openConnection(tableConnection); + tableConnection.setClient(client); + + assertThrows(SQLException.class, () -> tableConnection.prepareStatement(null)); + + verify(client, never()).requestStatementId(anyLong()); + verify(client, never()).prepareStatement(any(TSPrepareReq.class)); + verify(client, never()).closeOperation(any()); + } + @Test public void testGetServerProperties() throws TException { + openConnection(connection); final String version = "v0.1"; @SuppressWarnings("serial") final List supportedAggregationTime = @@ -116,7 +234,234 @@ public void testGetServerProperties() throws TException { @Test public void setTimeoutTest() throws SQLException { + openConnection(connection); connection.setQueryTimeout(60); Assert.assertEquals(60, connection.getQueryTimeout()); } + + @Test + public void testStandardConnectionStateMethods() throws Exception { + openConnection(connection); + + assertFalse(connection.getAutoCommit()); + connection.setAutoCommit(true); + assertTrue(connection.getAutoCommit()); + assertEquals("Apache IoTDB", connection.getCatalog()); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, connection.getHoldability()); + assertEquals(Connection.TRANSACTION_NONE, connection.getTransactionIsolation()); + assertFalse(connection.isReadOnly()); + connection.setReadOnly(false); + assertTrue(connection.isValid(0)); + } + + @Test(expected = SQLException.class) + public void testIsValidRejectsNegativeTimeout() throws SQLException { + connection.isValid(-1); + } + + @Test + public void testWrapperMethods() throws Exception { + openConnection(connection); + + assertTrue(connection.isWrapperFor(IoTDBConnection.class)); + assertTrue(connection.isWrapperFor(Connection.class)); + assertFalse(connection.isWrapperFor(String.class)); + assertFalse(connection.isWrapperFor(null)); + assertSame(connection, connection.unwrap(IoTDBConnection.class)); + assertSame(connection, connection.unwrap(Connection.class)); + } + + @Test(expected = SQLException.class) + public void testUnwrapRejectsUnsupportedClass() throws Exception { + openConnection(connection); + + connection.unwrap(String.class); + } + + @Test + public void testClosedConnectionRejectsOperations() throws SQLException { + assertTrue(connection.isClosed()); + assertFalse(connection.isValid(0)); + + assertThrows(SQLException.class, () -> connection.isWrapperFor(Connection.class)); + assertThrows(SQLException.class, () -> connection.unwrap(Connection.class)); + assertThrows(SQLException.class, () -> connection.abort(null)); + assertThrows(SQLException.class, () -> connection.clearWarnings()); + assertThrows(SQLException.class, () -> connection.commit()); + assertThrows(SQLException.class, () -> connection.createArrayOf("TEXT", new Object[0])); + assertThrows(SQLException.class, () -> connection.createBlob()); + assertThrows(SQLException.class, () -> connection.createClob()); + assertThrows(SQLException.class, () -> connection.createNClob()); + assertThrows(SQLException.class, () -> connection.createSQLXML()); + assertThrows(SQLException.class, () -> connection.createStatement()); + assertThrows( + SQLException.class, + () -> connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); + assertThrows( + SQLException.class, + () -> + connection.createStatement( + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertThrows(SQLException.class, () -> connection.createStruct("TEXT", new Object[0])); + assertThrows(SQLException.class, () -> connection.prepareStatement("SELECT ?")); + assertThrows(SQLException.class, () -> connection.prepareStatement("SELECT ?", 0)); + assertThrows(SQLException.class, () -> connection.prepareStatement("SELECT ?", new int[0])); + assertThrows(SQLException.class, () -> connection.prepareStatement("SELECT ?", new String[0])); + assertThrows( + SQLException.class, + () -> + connection.prepareStatement( + "SELECT ?", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); + assertThrows( + SQLException.class, + () -> + connection.prepareStatement( + "SELECT ?", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertThrows(SQLException.class, () -> connection.prepareCall("CALL x")); + assertThrows( + SQLException.class, + () -> + connection.prepareCall( + "CALL x", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); + assertThrows( + SQLException.class, + () -> + connection.prepareCall( + "CALL x", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertThrows(SQLException.class, () -> connection.getAutoCommit()); + assertThrows(SQLException.class, () -> connection.setAutoCommit(true)); + assertThrows(SQLException.class, () -> connection.getCatalog()); + assertThrows(SQLException.class, () -> connection.setCatalog("root")); + assertThrows(SQLException.class, () -> connection.getClientInfo()); + assertThrows(SQLException.class, () -> connection.getClientInfo("time_zone")); + SQLClientInfoException clientInfoException = + assertThrows( + SQLClientInfoException.class, () -> connection.setClientInfo("time_zone", "+07:00")); + assertTrue(clientInfoException.getMessage().contains("connection has been closed")); + clientInfoException = + assertThrows( + SQLClientInfoException.class, () -> connection.setClientInfo(new Properties())); + assertTrue(clientInfoException.getMessage().contains("connection has been closed")); + assertThrows(SQLException.class, () -> connection.getHoldability()); + assertThrows( + SQLException.class, () -> connection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertThrows(SQLException.class, () -> connection.getMetaData()); + assertThrows(SQLException.class, () -> connection.getNetworkTimeout()); + assertThrows(SQLException.class, () -> connection.getSchema()); + assertThrows(SQLException.class, () -> connection.setSchema("root")); + assertThrows(SQLException.class, () -> connection.getTransactionIsolation()); + assertThrows( + SQLException.class, () -> connection.setTransactionIsolation(Connection.TRANSACTION_NONE)); + assertThrows(SQLException.class, () -> connection.getTypeMap()); + assertThrows(SQLException.class, () -> connection.setTypeMap(null)); + assertThrows(SQLException.class, () -> connection.getWarnings()); + assertThrows(SQLException.class, () -> connection.isReadOnly()); + assertThrows(SQLException.class, () -> connection.setReadOnly(false)); + assertThrows(SQLException.class, () -> connection.nativeSQL("SELECT 1")); + assertThrows(SQLException.class, () -> connection.releaseSavepoint(null)); + assertThrows(SQLException.class, () -> connection.rollback()); + assertThrows(SQLException.class, () -> connection.rollback((Savepoint) null)); + assertThrows(SQLException.class, () -> connection.setNetworkTimeout(null, 0)); + assertThrows(SQLException.class, () -> connection.getQueryTimeout()); + assertThrows(SQLException.class, () -> connection.setQueryTimeout(60)); + assertThrows(SQLException.class, () -> connection.setSavepoint()); + assertThrows(SQLException.class, () -> connection.setSavepoint("s")); + assertThrows(IoTDBSQLException.class, () -> connection.setTimeZone("+07:00")); + assertThrows(TException.class, () -> connection.getServerProperties()); + } + + @Test + public void testClosedConnectionDoesNotReconnect() { + TTransport transport = org.mockito.Mockito.mock(TTransport.class); + setTransport(connection, transport); + + assertFalse(connection.reconnect()); + verify(transport, never()).close(); + } + + @Test + public void testCloseClosesCreatedStatements() throws Exception { + openConnection(connection); + connection.setClient(client); + when(client.requestStatementId(anyLong())).thenReturn(1L, 2L); + when(client.closeOperation(any())).thenReturn(successStatus); + when(client.closeSession(any())).thenReturn(successStatus); + + Statement statement = connection.createStatement(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT ?"); + + connection.close(); + + assertTrue(connection.isClosed()); + assertTrue(statement.isClosed()); + assertTrue(preparedStatement.isClosed()); + verify(client, times(2)).closeOperation(any()); + verify(client).closeSession(any()); + } + + @Test + public void testCloseClosesCurrentResultSetFromCreatedStatement() throws Exception { + openConnection(connection); + connection.setClient(client); + when(client.requestStatementId(anyLong())).thenReturn(1L); + when(client.closeOperation(any())).thenReturn(successStatus); + when(client.closeSession(any())).thenReturn(successStatus); + + IoTDBStatement statement = (IoTDBStatement) connection.createStatement(); + ResultSet resultSet = mock(ResultSet.class); + statement.resultSet = resultSet; + + connection.close(); + + assertTrue(statement.isClosed()); + verify(resultSet).close(); + verify(client).closeOperation(any()); + verify(client).closeSession(any()); + } + + @Test + public void testClosedStatementIsUnregisteredFromConnection() throws Exception { + openConnection(connection); + connection.setClient(client); + when(client.requestStatementId(anyLong())).thenReturn(1L); + when(client.closeOperation(any())).thenReturn(successStatus); + when(client.closeSession(any())).thenReturn(successStatus); + + Statement statement = connection.createStatement(); + statement.close(); + + connection.close(); + + assertTrue(statement.isClosed()); + verify(client, times(1)).closeOperation(any()); + verify(client).closeSession(any()); + } + + private void openConnection(IoTDBConnection target) { + try { + Field isClosedField = IoTDBConnection.class.getDeclaredField("isClosed"); + isClosedField.setAccessible(true); + isClosedField.setBoolean(target, false); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + private void setTransport(IoTDBConnection target, TTransport transport) { + try { + Field transportField = IoTDBConnection.class.getDeclaredField("transport"); + transportField.setAccessible(true); + transportField.set(target, transport); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } } 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 new file mode 100644 index 0000000000000..724917d908117 --- /dev/null +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDataSourceFactoryTest.java @@ -0,0 +1,241 @@ +/* + * 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. + */ + +package org.apache.iotdb.jdbc; + +import org.junit.Test; +import org.osgi.service.jdbc.DataSourceFactory; + +import javax.sql.DataSource; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class IoTDBDataSourceFactoryTest { + + @Test + public void testCreateDataSourceAllowsNullProperties() throws SQLException { + DataSource dataSource = new IoTDBDataSourceFactory().createDataSource(null); + + assertTrue(dataSource instanceof IoTDBDataSource); + IoTDBDataSource iotdbDataSource = (IoTDBDataSource) dataSource; + assertNull(iotdbDataSource.getUrl()); + assertNull(iotdbDataSource.getUser()); + assertNull(iotdbDataSource.getPassword()); + } + + @Test + public void testCreateDataSourceAllowsUrlOnlyProperties() throws SQLException { + String url = "jdbc:iotdb://localhost:6667"; + Properties properties = new Properties(); + properties.setProperty(DataSourceFactory.JDBC_URL, url); + + IoTDBDataSource dataSource = + (IoTDBDataSource) new IoTDBDataSourceFactory().createDataSource(properties); + + assertEquals(url, dataSource.getUrl()); + assertNull(dataSource.getUser()); + assertNull(dataSource.getPassword()); + assertEquals(url, properties.getProperty(DataSourceFactory.JDBC_URL)); + } + + @Test + public void testCreateDataSourceSupportsStandardServerPortAndDatabaseProperties() + throws SQLException { + Properties properties = new Properties(); + properties.setProperty(DataSourceFactory.JDBC_SERVER_NAME, "127.0.0.1"); + properties.put(DataSourceFactory.JDBC_PORT_NUMBER, 6688); + properties.setProperty(DataSourceFactory.JDBC_DATABASE_NAME, "root.sg"); + + IoTDBDataSource dataSource = + (IoTDBDataSource) new IoTDBDataSourceFactory().createDataSource(properties); + + assertEquals("127.0.0.1", dataSource.getServerName()); + assertEquals(Integer.valueOf(6688), dataSource.getPortNumber()); + assertEquals("root.sg", dataSource.getDatabaseName()); + assertNull(dataSource.getUrl()); + assertEquals("jdbc:iotdb://127.0.0.1:6688/root.sg", dataSource.getConnectionUrl()); + assertEquals(Integer.valueOf(6688), properties.get(DataSourceFactory.JDBC_PORT_NUMBER)); + } + + @Test + public void testCreateDataSourceAcceptsStandardInformationalAndPoolProperties() + throws SQLException { + Properties properties = new Properties(); + properties.setProperty(DataSourceFactory.JDBC_DATASOURCE_NAME, "iotdb-ds"); + properties.setProperty(DataSourceFactory.JDBC_DESCRIPTION, "IoTDB test data source"); + properties.setProperty(DataSourceFactory.JDBC_NETWORK_PROTOCOL, "tcp"); + properties.setProperty(DataSourceFactory.JDBC_ROLE_NAME, "reader"); + properties.put(DataSourceFactory.JDBC_INITIAL_POOL_SIZE, 1); + properties.put(DataSourceFactory.JDBC_MAX_POOL_SIZE, 2); + + IoTDBDataSource dataSource = + (IoTDBDataSource) new IoTDBDataSourceFactory().createDataSource(properties); + + assertEquals("iotdb-ds", dataSource.getDataSourceName()); + assertEquals("IoTDB test data source", dataSource.getDescription()); + assertEquals("tcp", dataSource.getNetworkProtocol()); + 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(); + properties.setProperty(DataSourceFactory.JDBC_PORT_NUMBER, "bad"); + + 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(); + + dataSource.setUrl("jdbc:iotdb://explicit:6667/root.explicit"); + dataSource.setServerName("127.0.0.1"); + dataSource.setPortNumber(6688); + dataSource.setDatabaseName("root.sg"); + + assertEquals("jdbc:iotdb://explicit:6667/root.explicit", dataSource.getConnectionUrl()); + } + + @Test + public void testDataSourceAllowsClearingUserAndPassword() { + IoTDBDataSource dataSource = new IoTDBDataSource(); + + dataSource.setUser("root"); + dataSource.setPassword("root"); + dataSource.setUser(null); + dataSource.setPassword(null); + + assertNull(dataSource.getUser()); + assertNull(dataSource.getPassword()); + } + + @Test + public void testDataSourceConstructorAllowsNullPort() { + IoTDBDataSource dataSource = + new IoTDBDataSource("jdbc:iotdb://localhost:6667", null, null, null); + + 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(); + + assertTrue(dataSource.isWrapperFor(IoTDBDataSource.class)); + assertTrue(dataSource.isWrapperFor(DataSource.class)); + assertFalse(dataSource.isWrapperFor(String.class)); + assertFalse(dataSource.isWrapperFor(null)); + assertSame(dataSource, dataSource.unwrap(IoTDBDataSource.class)); + assertSame(dataSource, dataSource.unwrap(DataSource.class)); + } + + @Test(expected = SQLException.class) + public void testDataSourceUnwrapRejectsUnsupportedClass() throws SQLException { + new IoTDBDataSource().unwrap(String.class); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void testDataSourceParentLoggerIsUnsupported() throws SQLException { + new IoTDBDataSource().getParentLogger(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void testConnectionPoolDataSourceIsUnsupported() throws SQLException { + new IoTDBDataSourceFactory().createConnectionPoolDataSource(null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void testXADataSourceIsUnsupported() throws SQLException { + new IoTDBDataSourceFactory().createXADataSource(null); + } + + @Test + public void testDataSourceStoresLogWriterAndLoginTimeout() throws SQLException { + IoTDBDataSource dataSource = new IoTDBDataSource(); + PrintWriter logWriter = new PrintWriter(new StringWriter()); + + dataSource.setLogWriter(logWriter); + dataSource.setLoginTimeout(10); + + assertSame(logWriter, dataSource.getLogWriter()); + assertEquals(10, dataSource.getLoginTimeout()); + } + + @Test(expected = SQLException.class) + public void testDataSourceRejectsNegativeLoginTimeout() throws SQLException { + new IoTDBDataSource().setLoginTimeout(-1); + } + + @Test(expected = SQLException.class) + public void testDataSourceConnectionWithCredentialsThrowsInvalidUrl() throws SQLException { + IoTDBDataSource dataSource = new IoTDBDataSource(); + + dataSource.setUrl("jdbc:iotdb://test"); + dataSource.getConnection("root", "root"); + } +} diff --git a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadataTest.java b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadataTest.java index ab29a14139986..1bce1264d291c 100644 --- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadataTest.java +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDatabaseMetadataTest.java @@ -27,6 +27,7 @@ import org.apache.iotdb.service.rpc.thrift.TSExecuteBatchStatementReq; import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq; import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp; +import org.apache.iotdb.service.rpc.thrift.TSFetchMetadataReq; import org.apache.thrift.TException; import org.junit.Assert; @@ -48,7 +49,13 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +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 IoTDBDatabaseMetadataTest { @@ -86,6 +93,43 @@ public void testGetAttributes() throws SQLException { Assert.assertEquals("PKTABLE_CAT", resultSet.getMetaData().getColumnName(2)); } + @Test + public void testWrapperMethods() throws SQLException { + assertTrue(databaseMetaData.isWrapperFor(IoTDBDatabaseMetadata.class)); + assertTrue(databaseMetaData.isWrapperFor(DatabaseMetaData.class)); + assertFalse(databaseMetaData.isWrapperFor(String.class)); + assertFalse(databaseMetaData.isWrapperFor(null)); + assertSame(databaseMetaData, databaseMetaData.unwrap(IoTDBDatabaseMetadata.class)); + assertSame(databaseMetaData, databaseMetaData.unwrap(DatabaseMetaData.class)); + } + + @Test(expected = SQLException.class) + public void testUnwrapRejectsUnsupportedClass() throws SQLException { + databaseMetaData.unwrap(String.class); + } + + @Test + public void testClosedConnectionRejectsMetadataOperations() throws SQLException, TException { + when(connection.isClosed()).thenReturn(true); + + assertThrows(SQLException.class, () -> databaseMetaData.isWrapperFor(DatabaseMetaData.class)); + assertThrows(SQLException.class, () -> databaseMetaData.unwrap(DatabaseMetaData.class)); + assertThrows(SQLException.class, () -> databaseMetaData.getConnection()); + assertThrows(SQLException.class, () -> databaseMetaData.getURL()); + assertThrows(SQLException.class, () -> databaseMetaData.getUserName()); + assertThrows(SQLException.class, () -> databaseMetaData.isReadOnly()); + assertThrows(SQLException.class, () -> databaseMetaData.getDatabaseProductVersion()); + assertThrows(SQLException.class, () -> databaseMetaData.getSystemFunctions()); + assertThrows(SQLException.class, () -> databaseMetaData.getMaxConnections()); + assertThrows(SQLException.class, () -> databaseMetaData.getMaxStatementLength()); + assertThrows(SQLException.class, () -> databaseMetaData.getDatabaseMajorVersion()); + assertThrows(SQLException.class, () -> databaseMetaData.getDatabaseMinorVersion()); + assertThrows( + SQLException.class, () -> ((IoTDBDatabaseMetadata) databaseMetaData).getMetadataInJson()); + assertEquals("", databaseMetaData.toString()); + verify(client, never()).fetchMetadata(any(TSFetchMetadataReq.class)); + } + @Test public void testGetBestRowIdentifier() throws SQLException { ResultSet resultSet = databaseMetaData.getBestRowIdentifier(null, null, null, 0, true); @@ -159,6 +203,16 @@ public void testGetImportedKeys() throws SQLException { Assert.assertEquals("PKTABLE_SCHEM", resultSet.getMetaData().getColumnName(3)); } + @Test + public void testGetPrimaryKeysBuildsMetadataResultSetAfterClosingInternalStatement() + throws SQLException { + ResultSet resultSet = databaseMetaData.getPrimaryKeys(null, null, "root.sg.d1"); + + Assert.assertEquals("TABLE_CAT", resultSet.getMetaData().getColumnName(1)); + Assert.assertEquals("TABLE_SCHEM", resultSet.getMetaData().getColumnName(2)); + Assert.assertEquals("TABLE_NAME", resultSet.getMetaData().getColumnName(3)); + } + @Test public void testGetIndexInfo() throws SQLException { ResultSet resultSet = databaseMetaData.getIndexInfo(null, null, null, false, false); 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 new file mode 100644 index 0000000000000..7a9173186c139 --- /dev/null +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBDriverTest.java @@ -0,0 +1,139 @@ +/* + * 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. + */ + +package org.apache.iotdb.jdbc; + +import org.junit.Test; + +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class IoTDBDriverTest { + + @Test + public void testAcceptsUrl() { + IoTDBDriver driver = new IoTDBDriver(); + + assertTrue(driver.acceptsURL("jdbc:iotdb://localhost:6667")); + assertFalse(driver.acceptsURL(null)); + assertFalse(driver.acceptsURL("jdbc:mysql://localhost:3306")); + } + + @Test + public void testDriverVersion() { + IoTDBDriver driver = new IoTDBDriver(); + + assertEquals(Config.DRIVER_MAJOR_VERSION, driver.getMajorVersion()); + assertEquals(Config.DRIVER_MINOR_VERSION, driver.getMinorVersion()); + } + + @Test + public void testGetPropertyInfo() throws SQLException { + IoTDBDriver driver = new IoTDBDriver(); + Properties properties = new Properties(); + properties.setProperty(Config.AUTH_USER, "root"); + properties.setProperty(Config.AUTH_PASSWORD, "secret"); + properties.setProperty(Config.USE_SSL, "true"); + properties.setProperty(Config.TRUST_STORE_PWD, "trust-store-secret"); + + DriverPropertyInfo[] propertyInfos = + driver.getPropertyInfo("jdbc:iotdb://localhost:6667", properties); + + assertTrue(propertyInfos.length > 0); + assertEquals("root", findProperty(propertyInfos, Config.AUTH_USER).value); + assertNull(findProperty(propertyInfos, Config.AUTH_PASSWORD).value); + assertNull(findProperty(propertyInfos, Config.TRUST_STORE_PWD).value); + assertEquals("true", findProperty(propertyInfos, Config.USE_SSL).value); + assertEquals( + Arrays.asList("true", "false"), + Arrays.asList(findProperty(propertyInfos, Config.USE_SSL).choices)); + assertEquals( + Arrays.asList(Constant.TREE, Constant.TABLE), + Arrays.asList(findProperty(propertyInfos, Config.SQL_DIALECT).choices)); + assertEquals(Config.DEFAULT_VERSION.name(), findProperty(propertyInfos, Config.VERSION).value); + } + + @Test + public void testGetPropertyInfoAllowsNullProperties() throws SQLException { + IoTDBDriver driver = new IoTDBDriver(); + + DriverPropertyInfo[] propertyInfos = + driver.getPropertyInfo("jdbc:iotdb://localhost:6667", null); + + assertNotNull(propertyInfos); + assertEquals(Config.DEFAULT_USER, findProperty(propertyInfos, Config.AUTH_USER).value); + } + + @Test + public void testGetPropertyInfoMergesUrlPropertiesWithoutMutatingInput() throws SQLException { + IoTDBDriver driver = new IoTDBDriver(); + Properties properties = new Properties(); + properties.setProperty(Config.USE_SSL, "false"); + boolean originalRpcCompression = Config.rpcThriftCompressionEnable; + Config.rpcThriftCompressionEnable = false; + try { + DriverPropertyInfo[] propertyInfos = + driver.getPropertyInfo( + "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); + assertEquals("true", findProperty(propertyInfos, Utils.RPC_COMPRESS).value); + assertEquals("false", properties.getProperty(Config.USE_SSL)); + assertFalse(Config.rpcThriftCompressionEnable); + } finally { + Config.rpcThriftCompressionEnable = originalRpcCompression; + } + } + + @Test(expected = SQLException.class) + public void testGetPropertyInfoRejectsInvalidIoTDBUrl() throws SQLException { + new IoTDBDriver().getPropertyInfo("jdbc:iotdb://localhost", new Properties()); + } + + @Test(expected = SQLException.class) + public void testGetPropertyInfoRejectsInvalidUrlProperties() throws SQLException { + new IoTDBDriver() + .getPropertyInfo("jdbc:iotdb://localhost:6667?network_timeout=-1", new Properties()); + } + + private static DriverPropertyInfo findProperty(DriverPropertyInfo[] propertyInfos, String name) { + for (DriverPropertyInfo propertyInfo : propertyInfos) { + if (name.equals(propertyInfo.name)) { + return propertyInfo; + } + } + fail("Missing driver property: " + name); + return null; + } +} 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 3fb60b8760da8..c091695d8b784 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 @@ -21,7 +21,9 @@ import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.rpc.RpcUtils; +import org.apache.iotdb.rpc.TSStatusCode; import org.apache.iotdb.service.rpc.thrift.IClientRPCService; +import org.apache.iotdb.service.rpc.thrift.TSCancelOperationReq; import org.apache.iotdb.service.rpc.thrift.TSCloseOperationReq; import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq; import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp; @@ -30,9 +32,11 @@ import org.apache.iotdb.service.rpc.thrift.TSFetchResultsReq; import org.apache.iotdb.service.rpc.thrift.TSFetchResultsResp; +import org.apache.tsfile.common.conf.TSFileConfig; import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.read.common.block.TsBlockBuilder; import org.apache.tsfile.read.common.block.column.TsBlockSerde; +import org.apache.tsfile.utils.Binary; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -43,6 +47,7 @@ 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; @@ -153,42 +158,7 @@ public void testQuery() throws Exception { /* * step 1: execute statement */ - List 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 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 columnIndex2TsBlockColumnIndexList = new ArrayList<>(columns.size()); - columnIndex2TsBlockColumnIndexList.add(0); - columnIndex2TsBlockColumnIndexList.add(1); - columnIndex2TsBlockColumnIndexList.add(2); - columnIndex2TsBlockColumnIndexList.add(0); - - when(execResp.getColumnIndex2TsBlockColumnIndexList()) - .thenReturn(columnIndex2TsBlockColumnIndexList); - doReturn("FLOAT") - .doReturn("INT64") - .doReturn("INT32") - .doReturn("FLOAT") - .when(fetchMetadataResp) - .getDataType(); + mockVehicleQueryResponse(); boolean hasResultSet = statement.execute(testSql); Assert.assertTrue(hasResultSet); @@ -201,11 +171,37 @@ public void testQuery() throws Exception { fetchResultsResp.hasResultSet = true; // at the first time to fetch try (ResultSet resultSet = statement.getResultSet()) { + Assert.assertTrue(resultSet.isWrapperFor(IoTDBJDBCResultSet.class)); + Assert.assertTrue(resultSet.isWrapperFor(ResultSet.class)); + Assert.assertFalse(resultSet.isWrapperFor(String.class)); + Assert.assertFalse(resultSet.isWrapperFor(null)); + Assert.assertSame(resultSet, resultSet.unwrap(IoTDBJDBCResultSet.class)); + Assert.assertSame(resultSet, resultSet.unwrap(ResultSet.class)); + Assert.assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, resultSet.getHoldability()); + Assert.assertEquals(ResultSet.FETCH_FORWARD, resultSet.getFetchDirection()); + resultSet.setFetchDirection(ResultSet.FETCH_FORWARD); + Assert.assertThrows( + SQLException.class, () -> resultSet.setFetchDirection(ResultSet.FETCH_REVERSE)); + Assert.assertEquals(Config.DEFAULT_FETCH_SIZE, resultSet.getFetchSize()); + resultSet.setFetchSize(123); + Assert.assertEquals(123, resultSet.getFetchSize()); + resultSet.setFetchSize(0); + Assert.assertEquals(Config.DEFAULT_FETCH_SIZE, resultSet.getFetchSize()); + Assert.assertThrows(SQLException.class, () -> resultSet.setFetchSize(-1)); + Assert.assertNull(resultSet.getWarnings()); + resultSet.clearWarnings(); + // check columnInfoMap Assert.assertEquals(1, resultSet.findColumn("Time")); Assert.assertEquals(2, resultSet.findColumn("root.vehicle.d0.s2")); Assert.assertEquals(3, resultSet.findColumn("root.vehicle.d0.s1")); Assert.assertEquals(4, resultSet.findColumn("root.vehicle.d0.s0")); + Assert.assertThrows(SQLException.class, () -> resultSet.findColumn("missing")); + Assert.assertThrows(SQLException.class, () -> resultSet.getString(0)); + Assert.assertThrows(SQLException.class, () -> resultSet.getObject("missing")); + Assert.assertThrows(SQLException.class, () -> resultSet.getTimestamp("missing")); + Assert.assertThrows( + SQLException.class, () -> ((IoTDBJDBCResultSet) resultSet).getColumnTypeByIndex(0)); ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // check columnInfoList @@ -228,7 +224,22 @@ public void testQuery() throws Exception { resultStr.append(resultSetMetaData.getColumnName(i)).append(","); } resultStr.append("\n"); + boolean firstRow = true; while (resultSet.next()) { // data + if (firstRow) { + Assert.assertNull(resultSet.getDate(4)); + Assert.assertTrue(resultSet.wasNull()); + Assert.assertNull(resultSet.getTime(4)); + Assert.assertTrue(resultSet.wasNull()); + Assert.assertNull(resultSet.getTimestamp(4)); + Assert.assertTrue(resultSet.wasNull()); + Assert.assertNull(resultSet.getBigDecimal(4, 2)); + Assert.assertTrue(resultSet.wasNull()); + Assert.assertNull(resultSet.getBigDecimal("root.vehicle.d0.s0", 2)); + Assert.assertTrue(resultSet.wasNull()); + Assert.assertThrows(SQLException.class, () -> resultSet.getBigDecimal(4, -1)); + firstRow = false; + } for (int i = 1; i <= colCount; i++) { resultStr.append(resultSet.getString(i)).append(","); resultObjectList.add(resultSet.getObject(i)); @@ -236,6 +247,7 @@ public void testQuery() throws Exception { resultStr.append("\n"); fetchResultsResp.hasResultSet = false; // at the second time to fetch } + Assert.assertFalse(resultSet.next()); String standard = "Time,root.vehicle.d0.s2,root.vehicle.d0.s1,root.vehicle.d0.s0,root.vehicle.d0.s2,\n" + "2,2.22,40000,null,2.22,\n" @@ -260,6 +272,295 @@ public void testQuery() throws Exception { verify(fetchResultsResp, times(0)).getStatus(); } + @SuppressWarnings("resource") + @Test + public void testTimestampByNameUsesConnectionTimePrecision() throws Exception { + when(connection.getTimeFactor()).thenReturn(1_000_000); + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + try (ResultSet resultSet = statement.getResultSet()) { + Assert.assertTrue(resultSet.next()); + Assert.assertEquals(resultSet.getTimestamp(1), resultSet.getTimestamp("Time")); + } + } + + @SuppressWarnings("resource") + @Test + public void testClosedResultSetRejectsCachedReads() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertTrue(resultSet.next()); + + resultSet.close(); + + Assert.assertTrue(resultSet.isClosed()); + Assert.assertThrows(SQLException.class, () -> resultSet.isWrapperFor(ResultSet.class)); + Assert.assertThrows(SQLException.class, () -> resultSet.unwrap(ResultSet.class)); + Assert.assertThrows(SQLException.class, () -> resultSet.clearWarnings()); + Assert.assertThrows(SQLException.class, () -> resultSet.next()); + Assert.assertThrows(SQLException.class, () -> resultSet.getBigDecimal(1)); + Assert.assertThrows(SQLException.class, () -> resultSet.getBlob(1)); + Assert.assertThrows(SQLException.class, () -> resultSet.getBytes(1)); + Assert.assertThrows(SQLException.class, () -> resultSet.getConcurrency()); + Assert.assertThrows(SQLException.class, () -> resultSet.getFetchDirection()); + Assert.assertThrows( + SQLException.class, () -> resultSet.setFetchDirection(ResultSet.FETCH_FORWARD)); + Assert.assertThrows(SQLException.class, () -> resultSet.getFetchSize()); + Assert.assertThrows(SQLException.class, () -> resultSet.setFetchSize(1)); + Assert.assertThrows(SQLException.class, () -> resultSet.getHoldability()); + Assert.assertThrows(SQLException.class, () -> resultSet.getStatement()); + Assert.assertThrows(SQLException.class, () -> resultSet.getString(1)); + Assert.assertThrows(SQLException.class, () -> resultSet.findColumn("Time")); + Assert.assertThrows(SQLException.class, () -> resultSet.getMetaData()); + Assert.assertThrows(SQLException.class, () -> resultSet.getType()); + Assert.assertThrows(SQLException.class, () -> resultSet.getWarnings()); + Assert.assertThrows(SQLException.class, () -> resultSet.wasNull()); + Assert.assertThrows( + SQLException.class, () -> ((IoTDBJDBCResultSet) resultSet).isSetTracingInfo()); + Assert.assertThrows( + SQLException.class, () -> ((IoTDBJDBCResultSet) resultSet).isIgnoreTimeStamp()); + Assert.assertThrows( + SQLException.class, () -> ((IoTDBJDBCResultSet) resultSet).getColumnTypeByIndex(1)); + Assert.assertThrows( + SQLException.class, () -> ((IoTDBJDBCResultSet) resultSet).getOperationType()); + + SQLException unsupportedException = + Assert.assertThrows(SQLException.class, () -> resultSet.absolute(1)); + Assert.assertEquals("ResultSet has been closed", unsupportedException.getMessage()); + unsupportedException = + Assert.assertThrows(SQLException.class, () -> resultSet.updateString(1, "x")); + Assert.assertEquals("ResultSet has been closed", unsupportedException.getMessage()); + } + + @SuppressWarnings("resource") + @Test + public void testLocalResultSetCloseDoesNotCloseServerOperation() throws Exception { + ResultSet resultSet = + new IoTDBJDBCResultSet( + statement, + Collections.singletonList("s1"), + Collections.singletonList("INT32"), + Collections.singletonMap("s1", 0), + true, + client, + null, + -1, + sessionId, + Collections.emptyList(), + null, + (long) 60 * 1000, + false, + zoneID); + + resultSet.close(); + + Assert.assertTrue(resultSet.isClosed()); + verify(client, times(0)).closeOperation(any(TSCloseOperationReq.class)); + } + + @SuppressWarnings("resource") + @Test + public void testResultSetCloseClearsStatementQueryId() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + resultSet.close(); + statement.cancel(); + + Assert.assertTrue(resultSet.isClosed()); + verify(client, times(0)).cancelOperation(any(TSCancelOperationReq.class)); + } + + @SuppressWarnings("resource") + @Test + public void testStatementCloseClosesCurrentResultSet() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertFalse(resultSet.isClosed()); + + statement.close(); + + Assert.assertTrue(statement.isClosed()); + Assert.assertTrue(resultSet.isClosed()); + Assert.assertThrows(SQLException.class, () -> resultSet.next()); + } + + @SuppressWarnings("resource") + @Test + public void testFailedCloseStillMarksResultSetClosed() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + TSStatus closeFailure = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode()); + closeFailure.setMessage("close failed"); + when(client.closeOperation(any(TSCloseOperationReq.class))).thenReturn(closeFailure); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertThrows(SQLException.class, resultSet::close); + + Assert.assertTrue(resultSet.isClosed()); + Assert.assertThrows(SQLException.class, resultSet::next); + } + + @SuppressWarnings("resource") + @Test + public void testStatementExecutionClosesPreviousResultSet() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet previousResultSet = statement.getResultSet(); + Assert.assertFalse(previousResultSet.isClosed()); + + mockVehicleQueryResponse(); + execResp.queryResult = FakedFirstFetchTsBlockResult(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + Assert.assertTrue(previousResultSet.isClosed()); + Assert.assertThrows(SQLException.class, () -> previousResultSet.next()); + + ResultSet currentResultSet = statement.getResultSet(); + Assert.assertNotSame(previousResultSet, currentResultSet); + Assert.assertFalse(currentResultSet.isClosed()); + + currentResultSet.close(); + } + + @SuppressWarnings("resource") + @Test + public void testExecuteUpdateClosesCurrentResultSet() throws Exception { + mockVehicleQueryResponse(); + when(client.executeUpdateStatement(any(TSExecuteStatementReq.class))).thenReturn(execResp); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertFalse(resultSet.isClosed()); + + Assert.assertEquals(0, statement.executeUpdate("insert into root.sg.d(time,s) values(1,1)")); + + Assert.assertTrue(resultSet.isClosed()); + Assert.assertNull(statement.getResultSet()); + } + + @SuppressWarnings("resource") + @Test + public void testGetMoreResultsClosesCurrentResultSet() throws Exception { + mockVehicleQueryResponse(); + + Assert.assertTrue(statement.execute("select * from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertFalse(resultSet.isClosed()); + + Assert.assertFalse(statement.getMoreResults()); + + Assert.assertTrue(resultSet.isClosed()); + Assert.assertNull(statement.getResultSet()); + } + + @SuppressWarnings("resource") + @Test + public void testExhaustedResultSetIsNotReportedClosed() throws Exception { + mockTextQueryResponse(); + + Assert.assertTrue(statement.execute("select s3 from root.vehicle.d0")); + + ResultSet resultSet = statement.getResultSet(); + Assert.assertTrue(resultSet.next()); + Assert.assertFalse(resultSet.next()); + Assert.assertFalse(resultSet.isClosed()); + Assert.assertEquals(ResultSet.TYPE_FORWARD_ONLY, resultSet.getType()); + + resultSet.close(); + + Assert.assertTrue(resultSet.isClosed()); + } + + @SuppressWarnings("resource") + @Test + public void testInvalidBigDecimalConversionThrowsSQLException() throws Exception { + mockTextQueryResponse(); + + Assert.assertTrue(statement.execute("select s3 from root.vehicle.d0")); + + try (ResultSet resultSet = statement.getResultSet()) { + Assert.assertTrue(resultSet.next()); + Assert.assertThrows(SQLException.class, () -> resultSet.getBigDecimal(2)); + Assert.assertThrows(SQLException.class, () -> resultSet.getBigDecimal("root.vehicle.d0.s3")); + } + } + + private void mockVehicleQueryResponse() { + List 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 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 columnIndex2TsBlockColumnIndexList = new ArrayList<>(columns.size()); + columnIndex2TsBlockColumnIndexList.add(0); + columnIndex2TsBlockColumnIndexList.add(1); + columnIndex2TsBlockColumnIndexList.add(2); + columnIndex2TsBlockColumnIndexList.add(0); + + when(execResp.getColumnIndex2TsBlockColumnIndexList()) + .thenReturn(columnIndex2TsBlockColumnIndexList); + doReturn("FLOAT") + .doReturn("INT64") + .doReturn("INT32") + .doReturn("FLOAT") + .when(fetchMetadataResp) + .getDataType(); + } + + private void mockTextQueryResponse() { + List columns = new ArrayList<>(Collections.singletonList("root.vehicle.d0.s3")); + List dataTypeList = new ArrayList<>(Collections.singletonList("TEXT")); + + 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); + when(execResp.getColumnIndex2TsBlockColumnIndexList()) + .thenReturn(new ArrayList<>(Collections.singletonList(0))); + execResp.queryResult = fakedTextFetchTsBlockResult(); + } + private void constructObjectList(List standardObject) { Object[][] input = { { @@ -363,4 +664,22 @@ private List FakedFirstFetchTsBlockResult() { return Collections.singletonList(tsBlock); } + + private List fakedTextFetchTsBlockResult() { + TsBlockBuilder tsBlockBuilder = new TsBlockBuilder(Collections.singletonList(TSDataType.TEXT)); + tsBlockBuilder.getTimeColumnBuilder().writeLong(1L); + tsBlockBuilder + .getColumnBuilder(0) + .writeBinary(new Binary("not-a-number", TSFileConfig.STRING_CHARSET)); + tsBlockBuilder.declarePosition(); + + ByteBuffer tsBlock = null; + try { + tsBlock = new TsBlockSerde().serialize(tsBlockBuilder.build()); + } catch (IOException e) { + e.printStackTrace(); + } + + return Collections.singletonList(tsBlock); + } } 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 f80b8a83936cd..349692274baf0 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,15 +31,27 @@ 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.ResultSet; import java.sql.SQLException; +import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.ZoneId; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +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.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -62,6 +74,18 @@ public void before() throws Exception { when(execStatementResp.getQueryId()).thenReturn(queryId); when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(execStatementResp); + when(client.closeOperation(any())).thenReturn(Status_SUCCESS); + } + + @SuppressWarnings("resource") + @Test + public void testConstructorRejectsNullSqlBeforeRequestingStatementId() throws Exception { + assertThrows( + SQLException.class, + () -> new IoTDBPreparedStatement(connection, client, sessionId, null, zoneId)); + + verify(client, never()).requestStatementId(anyLong()); + verify(client, never()).closeOperation(any()); } @SuppressWarnings("resource") @@ -83,13 +107,89 @@ public void testNonParameterized() throws Exception { @SuppressWarnings("resource") @Test - public void unusedArgument() throws SQLException { + public void testParameterMetadataWrapperMethods() throws Exception { + IoTDBPreparedStatement ps = + new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ParameterMetaData metadata = ps.getParameterMetaData(); + + assertTrue(metadata.isWrapperFor(ParameterMetaData.class)); + assertFalse(metadata.isWrapperFor(String.class)); + assertFalse(metadata.isWrapperFor(null)); + assertSame(metadata, metadata.unwrap(ParameterMetaData.class)); + assertThrows(SQLException.class, () -> metadata.unwrap(String.class)); + } + + @SuppressWarnings("resource") + @Test + public void testParameterMetadataRejectsUnsetIndexAndHandlesNullValue() throws Exception { + IoTDBPreparedStatement ps = + new IoTDBPreparedStatement( + connection, client, sessionId, "SELECT ? FROM root.sg.d WHERE s = '?'", zoneId); + ParameterMetaData metadata = ps.getParameterMetaData(); + + assertEquals(1, metadata.getParameterCount()); + assertEquals(0, metadata.getPrecision(1)); + assertThrows(SQLException.class, () -> metadata.getPrecision(0)); + assertThrows(SQLException.class, () -> metadata.getParameterMode(2)); + assertThrows(SQLException.class, () -> ps.setString(0, "x")); + assertThrows(SQLException.class, () -> ps.setInt(2, 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 getMetaDataReturnsNullWhenNoResultSetExists() throws Exception { + IoTDBPreparedStatement ps = + new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + + assertNull(ps.getMetaData()); + } + + @SuppressWarnings("resource") + @Test + public void testClosedPreparedStatementRejectsParameterOperations() throws Exception { + IoTDBPreparedStatement ps = + new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ParameterMetaData metadata = ps.getParameterMetaData(); + + ps.close(); + + assertTrue(ps.isClosed()); + assertThrows(SQLException.class, () -> ps.clearParameters()); + assertThrows(SQLException.class, () -> ps.setInt(1, 1)); + assertThrows(SQLException.class, () -> ps.setString(1, "x")); + assertThrows( + SQLException.class, + () -> ps.setBinaryStream(1, new ByteArrayInputStream(new byte[] {1}), 1)); + assertThrows(SQLException.class, () -> ps.getParameterMetaData()); + assertThrows(SQLException.class, () -> metadata.isWrapperFor(ParameterMetaData.class)); + assertThrows(SQLException.class, () -> metadata.unwrap(ParameterMetaData.class)); + assertThrows(SQLException.class, () -> metadata.getParameterCount()); + assertThrows(SQLException.class, () -> metadata.getPrecision(1)); + + SQLException unsupportedException = + assertThrows(SQLException.class, () -> ps.setArray(1, null)); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, () -> ps.setRowId(1, null)); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, () -> ps.setObject(1, new Object())); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + } + + @SuppressWarnings("resource") + @Test + public void invalidParameterIndex() throws SQLException { String sql = "SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 24 and time > 2017-11-1 0:13:00"; IoTDBPreparedStatement ps = new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId); - ps.setString(1, "123"); - assertFalse(ps.execute()); + assertThrows(SQLException.class, () -> ps.setString(1, "123")); } @SuppressWarnings("resource") @@ -102,6 +202,20 @@ public void unsetArgument() throws SQLException { assertThrows(SQLException.class, () -> ps.execute()); } + @SuppressWarnings("resource") + @Test + public void executeWithUnsetParameterClosesPreviousResultSet() throws SQLException { + IoTDBPreparedStatement ps = + new IoTDBPreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ResultSet previousResultSet = mock(ResultSet.class); + ps.resultSet = previousResultSet; + + assertThrows(SQLException.class, ps::execute); + + verify(previousResultSet).close(); + assertNull(ps.resultSet); + } + @SuppressWarnings("resource") @Test public void oneIntArgument() throws Exception { @@ -236,6 +350,40 @@ public void oneStringArgument3() throws Exception { "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 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/IoTDBRelationalDatabaseMetadataTest.java b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBRelationalDatabaseMetadataTest.java new file mode 100644 index 0000000000000..c4c9547ca4898 --- /dev/null +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBRelationalDatabaseMetadataTest.java @@ -0,0 +1,144 @@ +/* + * 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. + */ + +package org.apache.iotdb.jdbc; + +import org.apache.iotdb.jdbc.relational.IoTDBRelationalDatabaseMetadata; +import org.apache.iotdb.service.rpc.thrift.IClientRPCService.Iface; + +import org.junit.Test; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class IoTDBRelationalDatabaseMetadataTest { + + @Test + public void testReadsSourceResultSetsBeforeClosingInternalStatements() throws SQLException { + IoTDBConnection connection = mock(IoTDBConnection.class); + Iface client = mock(Iface.class); + long sessionId = 1L; + ZoneId zoneId = ZoneId.systemDefault(); + when(connection.getTimeFactor()).thenReturn(1_000); + + CloseAwareStatement tablesStatement = + new CloseAwareStatement(connection, client, sessionId, zoneId); + ResultSet tablesSource = + sourceResultSet(tablesStatement, values("table_name", "table1", "comment", "")); + tablesStatement.setResultSet(tablesSource); + + CloseAwareStatement columnsStatement = + new CloseAwareStatement(connection, client, sessionId, zoneId); + ResultSet columnsSource = + sourceResultSet( + columnsStatement, values("column_name", "tag1", "datatype", "INT32", "comment", "")); + columnsStatement.setResultSet(columnsSource); + + CloseAwareStatement primaryKeysStatement = + new CloseAwareStatement(connection, client, sessionId, zoneId); + ResultSet primaryKeysSource = + sourceResultSet(primaryKeysStatement, values("column_name", "tag1", "category", "TAG")); + primaryKeysStatement.setResultSet(primaryKeysSource); + + when(connection.createStatement()) + .thenReturn(tablesStatement, columnsStatement, primaryKeysStatement); + + IoTDBRelationalDatabaseMetadata metadata = + new IoTDBRelationalDatabaseMetadata(connection, client, sessionId, zoneId); + + ResultSet tables = metadata.getTables(null, "rootdb", null, null); + assertNotNull(tables); + assertEquals("TABLE_SCHEM", tables.getMetaData().getColumnName(1)); + + ResultSet columns = metadata.getColumns(null, "rootdb", "table1", null); + assertEquals("COLUMN_NAME", columns.getMetaData().getColumnName(4)); + + ResultSet primaryKeys = metadata.getPrimaryKeys(null, "rootdb", "table1"); + assertEquals("PK_NAME", primaryKeys.getMetaData().getColumnName(6)); + + verify(tablesSource).close(); + verify(columnsSource).close(); + verify(primaryKeysSource).close(); + } + + private static ResultSet sourceResultSet( + CloseAwareStatement statement, Map values) throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + AtomicInteger nextCalls = new AtomicInteger(); + when(resultSet.next()) + .thenAnswer( + invocation -> { + if (statement.wasClosed()) { + throw new SQLException("source result set was closed with its statement"); + } + return nextCalls.getAndIncrement() == 0; + }); + when(resultSet.getString(anyString())) + .thenAnswer(invocation -> values.get(invocation.getArgument(0))); + return resultSet; + } + + private static Map values(String... entries) { + Map values = new HashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + values.put(entries[i], entries[i + 1]); + } + return values; + } + + private static class CloseAwareStatement extends IoTDBStatement { + private ResultSet resultSet; + private boolean closed; + + private CloseAwareStatement( + IoTDBConnection connection, Iface client, long sessionId, ZoneId zoneId) { + super(connection, client, sessionId, zoneId, 0, -1L); + } + + private void setResultSet(ResultSet resultSet) { + this.resultSet = resultSet; + } + + private boolean wasClosed() { + return closed; + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + return resultSet; + } + + @Override + public void close() throws SQLException { + closed = true; + super.close(); + } + } +} diff --git a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBResultMetadataTest.java b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBResultMetadataTest.java index dc9869bddda8e..69afa7510931f 100644 --- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBResultMetadataTest.java +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBResultMetadataTest.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Test; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; @@ -32,6 +33,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class IoTDBResultMetadataTest { @@ -173,4 +177,106 @@ public void testGetColumnType() throws SQLException { assertEquals(metadata.getColumnType(i + 1), types[i - 1]); } } + + @Test + public void testMetadataColumnMethodsRejectInvalidIndex() throws SQLException { + metadata = + new IoTDBResultMetadata( + false, + null, + "QUERY", + Arrays.asList("Time", "root.a.b.c1"), + Arrays.asList("TIMESTAMP", "INT32"), + false); + + assertThrows(SQLException.class, () -> metadata.getCatalogName(0)); + assertThrows(SQLException.class, () -> metadata.isAutoIncrement(0)); + assertThrows(SQLException.class, () -> metadata.isNullable(3)); + assertFalse(metadata.isAutoIncrement(1)); + assertTrue(metadata.isCaseSensitive(1)); + assertEquals(ResultSetMetaData.columnNullable, metadata.isNullable(1)); + } + + @Test + public void testMissingColumnTypeMetadataThrowsSQLException() { + metadata = + new IoTDBResultMetadata( + false, + null, + "QUERY", + Arrays.asList("Time", "root.a.b.c1"), + Collections.singletonList("TIMESTAMP"), + false); + + assertThrows(SQLException.class, () -> metadata.getColumnType(2)); + assertThrows(SQLException.class, () -> metadata.getColumnTypeName(2)); + assertThrows(SQLException.class, () -> metadata.getPrecision(2)); + assertThrows(SQLException.class, () -> metadata.getScale(2)); + } + + @Test + public void testNullColumnTypeMetadataThrowsSQLException() { + metadata = + new IoTDBResultMetadata( + false, + null, + "QUERY", + Arrays.asList("Time", "root.a.b.c1"), + Arrays.asList("TIMESTAMP", null), + false); + + assertThrows(SQLException.class, () -> metadata.getColumnType(2)); + assertThrows(SQLException.class, () -> metadata.getColumnTypeName(2)); + assertThrows(SQLException.class, () -> metadata.getPrecision(2)); + assertThrows(SQLException.class, () -> metadata.getScale(2)); + } + + @Test + public void testCatalogAndSchemaHandleMissingStorageGroupMetadata() throws SQLException { + metadata = + new IoTDBResultMetadata( + false, + null, + "QUERY", + Arrays.asList("Time", "root.a.b.c1"), + Arrays.asList("TIMESTAMP", "INT32"), + false); + + assertEquals("", metadata.getCatalogName(2)); + assertEquals("", metadata.getSchemaName(2)); + } + + @Test + public void testUnknownColumnTypeClassNameReturnsNull() throws SQLException { + metadata = + new IoTDBResultMetadata( + false, + null, + "QUERY", + Arrays.asList("Time", "root.a.b.c1"), + Arrays.asList("TIMESTAMP", "UNKNOWN"), + false); + + assertEquals(0, metadata.getColumnType(2)); + assertNull(metadata.getColumnTypeName(2)); + assertNull(metadata.getColumnClassName(2)); + } + + @Test + public void testWrapperMethods() throws SQLException { + metadata = new IoTDBResultMetadata(false, null, "QUERY", null, null, false); + + assertTrue(metadata.isWrapperFor(IoTDBResultMetadata.class)); + assertTrue(metadata.isWrapperFor(ResultSetMetaData.class)); + assertFalse(metadata.isWrapperFor(String.class)); + assertFalse(metadata.isWrapperFor(null)); + assertSame(metadata, metadata.unwrap(IoTDBResultMetadata.class)); + assertSame(metadata, metadata.unwrap(ResultSetMetaData.class)); + } + + @Test(expected = SQLException.class) + public void testUnwrapRejectsUnsupportedClass() throws SQLException { + metadata = new IoTDBResultMetadata(false, null, "QUERY", null, null, false); + metadata.unwrap(String.class); + } } diff --git a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBStatementTest.java b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBStatementTest.java index 5cfd5342e05e6..0b4fbbb191dc3 100644 --- a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBStatementTest.java +++ b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBStatementTest.java @@ -21,6 +21,10 @@ import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.service.rpc.thrift.IClientRPCService.Iface; +import org.apache.iotdb.service.rpc.thrift.TSCancelOperationReq; +import org.apache.iotdb.service.rpc.thrift.TSCloseOperationReq; +import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq; +import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp; import org.apache.iotdb.service.rpc.thrift.TSFetchMetadataReq; import org.apache.iotdb.service.rpc.thrift.TSFetchMetadataResp; @@ -29,14 +33,28 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.nio.ByteBuffer; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.time.ZoneId; +import java.util.Collections; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +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.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class IoTDBStatementTest { @@ -106,4 +124,178 @@ public void setTimeoutTest() throws SQLException { statement.setQueryTimeout(100); Assert.assertEquals(100, statement.getQueryTimeout()); } + + @Test(expected = SQLException.class) + public void testSetQueryTimeoutRejectsNegativeValue() throws SQLException { + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID); + statement.setQueryTimeout(-1); + } + + @Test + public void testWrapperMethods() throws SQLException { + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID); + + assertTrue(statement.isWrapperFor(IoTDBStatement.class)); + assertTrue(statement.isWrapperFor(Statement.class)); + assertFalse(statement.isWrapperFor(String.class)); + assertFalse(statement.isWrapperFor(null)); + assertSame(statement, statement.unwrap(IoTDBStatement.class)); + assertSame(statement, statement.unwrap(Statement.class)); + } + + @Test + public void testStandardResultSetHoldability() throws SQLException { + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID); + + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + } + + @Test + public void testClosedStatementRejectsOperations() throws SQLException { + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID, 0, -1L); + + statement.close(); + + assertTrue(statement.isClosed()); + assertThrows(SQLException.class, () -> statement.execute("select 1")); + assertThrows(SQLException.class, () -> statement.executeQuery("select 1")); + assertThrows( + SQLException.class, + () -> statement.executeUpdate("insert into root.sg.d(time,s) values(1,1)")); + assertThrows(SQLException.class, () -> statement.executeBatch()); + assertThrows(SQLException.class, () -> statement.addBatch("select 1")); + assertThrows(SQLException.class, () -> statement.clearBatch()); + assertThrows(SQLException.class, () -> statement.isWrapperFor(Statement.class)); + assertThrows(SQLException.class, () -> statement.unwrap(Statement.class)); + assertThrows(SQLException.class, () -> statement.getFetchSize()); + assertThrows(SQLException.class, () -> statement.getWarnings()); + + SQLException unsupportedException = + assertThrows( + SQLException.class, + () -> statement.execute("select 1", Statement.RETURN_GENERATED_KEYS)); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, () -> statement.getGeneratedKeys()); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, statement::closeOnCompletion); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + } + + @Test + public void testCloseStillClosesOperationWhenResultSetCloseFails() throws Exception { + when(client.closeOperation(any())).thenReturn(RpcUtils.SUCCESS_STATUS); + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID, 0, 1L); + ResultSet resultSet = mock(ResultSet.class); + doThrow(new SQLException("result set close failed")).when(resultSet).close(); + statement.resultSet = resultSet; + + SQLException closeException = assertThrows(SQLException.class, statement::close); + + assertEquals("result set close failed", closeException.getMessage()); + assertTrue(statement.isClosed()); + assertNull(statement.resultSet); + verify(client).closeOperation(any()); + } + + @Test + public void testExecuteClosesQueryOperationWhenResultSetCreationFails() throws Exception { + long queryId = 10L; + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID, 0, 1L); + TSExecuteStatementResp resp = malformedQueryResponse(queryId); + when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(resp); + when(client.closeOperation(any(TSCloseOperationReq.class))).thenReturn(RpcUtils.SUCCESS_STATUS); + + assertThrows(SQLException.class, () -> statement.execute("select s1 from root.sg.d")); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(1L, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + assertNull(statement.resultSet); + } + + @Test + public void testExecuteQueryClosesQueryOperationWhenResultSetCreationFails() throws Exception { + long queryId = 11L; + IoTDBStatement statement = new IoTDBStatement(connection, client, sessionId, zoneID, 0, 2L); + TSExecuteStatementResp resp = malformedQueryResponse(queryId); + when(client.executeQueryStatementV2(any(TSExecuteStatementReq.class))).thenReturn(resp); + when(client.closeOperation(any(TSCloseOperationReq.class))).thenReturn(RpcUtils.SUCCESS_STATUS); + + assertThrows(SQLException.class, () -> statement.executeQuery("select s1 from root.sg.d")); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(2L, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + assertNull(statement.resultSet); + } + + @Test + public void testExecuteClosesUnexpectedQueryOperationForNonResultResponse() throws Exception { + long statementId = 3L; + long queryId = 12L; + IoTDBStatement statement = + new IoTDBStatement(connection, client, sessionId, zoneID, 0, statementId); + TSExecuteStatementResp resp = new TSExecuteStatementResp(); + resp.setStatus(RpcUtils.SUCCESS_STATUS); + resp.setQueryId(queryId); + when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(resp); + when(client.closeOperation(any(TSCloseOperationReq.class))).thenReturn(RpcUtils.SUCCESS_STATUS); + + assertFalse(statement.execute("insert into root.sg.d(time,s) values(1,1)")); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(statementId, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + + statement.cancel(); + + verify(client, never()).cancelOperation(any(TSCancelOperationReq.class)); + } + + @Test + public void testExecuteUpdateClosesUnexpectedQueryOperation() throws Exception { + long statementId = 4L; + long queryId = 13L; + IoTDBStatement statement = + new IoTDBStatement(connection, client, sessionId, zoneID, 0, statementId); + TSExecuteStatementResp resp = new TSExecuteStatementResp(); + resp.setStatus(RpcUtils.SUCCESS_STATUS); + resp.setQueryId(queryId); + when(client.executeUpdateStatement(any(TSExecuteStatementReq.class))).thenReturn(resp); + when(client.closeOperation(any(TSCloseOperationReq.class))).thenReturn(RpcUtils.SUCCESS_STATUS); + + assertEquals(0, statement.executeUpdate("insert into root.sg.d(time,s) values(1,1)")); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(statementId, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + + statement.cancel(); + + verify(client, never()).cancelOperation(any(TSCancelOperationReq.class)); + } + + @Test(expected = SQLException.class) + public void testUnwrapRejectsUnsupportedClass() throws SQLException { + new IoTDBStatement(connection, client, sessionId, zoneID).unwrap(String.class); + } + + private TSExecuteStatementResp malformedQueryResponse(long queryId) { + TSExecuteStatementResp resp = new TSExecuteStatementResp(); + resp.setStatus(RpcUtils.SUCCESS_STATUS); + resp.setQueryId(queryId); + resp.setColumns(Collections.singletonList("s1")); + resp.setDataTypeList(Collections.emptyList()); + resp.setQueryResult(Collections.emptyList()); + resp.setIgnoreTimeStamp(true); + return resp; + } } 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 dd1c7ecbcb209..3e3f54e152111 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,24 +21,54 @@ 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.TSCancelOperationReq; +import org.apache.iotdb.service.rpc.thrift.TSCloseOperationReq; 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; +import org.apache.thrift.TException; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; 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.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; import java.time.ZoneId; - +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +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.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +88,7 @@ public class IoTDBTablePreparedStatementTest { public void before() throws Exception { MockitoAnnotations.initMocks(this); when(connection.getSqlDialect()).thenReturn("table"); + when(connection.getTimeFactor()).thenReturn(1_000); when(execStatementResp.getStatus()).thenReturn(Status_SUCCESS); when(execStatementResp.getQueryId()).thenReturn(queryId); @@ -81,6 +112,9 @@ public TSPrepareResp answer(InvocationOnMock invocation) throws Throwable { // Mock for executePreparedStatement when(client.executePreparedStatement(any(TSExecutePreparedReq.class))) .thenReturn(execStatementResp); + when(client.executeStatementV2(any(TSExecuteStatementReq.class))).thenReturn(execStatementResp); + when(client.deallocatePreparedStatement(any())).thenReturn(Status_SUCCESS); + when(client.closeOperation(any())).thenReturn(Status_SUCCESS); } /** Count the number of '?' placeholders in a SQL string, ignoring those inside quotes */ @@ -111,6 +145,261 @@ private int countQuestionMarks(String sql) { // ========== Table Model SQL Injection Prevention Tests ========== + @SuppressWarnings("resource") + @Test + public void testConstructorRejectsNullSqlBeforeRequestingStatementId() throws Exception { + assertThrows( + SQLException.class, + () -> new IoTDBTablePreparedStatement(connection, client, sessionId, null, zoneId)); + + verify(client, never()).requestStatementId(anyLong()); + verify(client, never()).prepareStatement(any(TSPrepareReq.class)); + verify(client, never()).closeOperation(any()); + } + + @SuppressWarnings("resource") + @Test + public void testParameterMetadataWrapperMethods() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ParameterMetaData metadata = ps.getParameterMetaData(); + + assertTrue(metadata.isWrapperFor(ParameterMetaData.class)); + assertFalse(metadata.isWrapperFor(String.class)); + assertFalse(metadata.isWrapperFor(null)); + assertSame(metadata, metadata.unwrap(ParameterMetaData.class)); + 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)); + assertThrows(SQLException.class, () -> ps.setInt(0, 1)); + assertThrows(SQLException.class, () -> ps.setInt(2, 1)); + + ps.setInt(1, 1); + + assertEquals(Types.INTEGER, metadata.getParameterType(1)); + assertTrue(metadata.isSigned(1)); + } + + @SuppressWarnings("resource") + @Test + public void testClosedTablePreparedStatementRejectsParameterOperations() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ParameterMetaData metadata = ps.getParameterMetaData(); + + ps.close(); + + assertTrue(ps.isClosed()); + assertThrows(SQLException.class, () -> ps.clearParameters()); + assertThrows(SQLException.class, () -> ps.getMetaData()); + assertThrows(SQLException.class, () -> ps.setInt(1, 1)); + assertThrows(SQLException.class, () -> ps.setString(1, "x")); + assertThrows( + SQLException.class, + () -> ps.setBinaryStream(1, new ByteArrayInputStream(new byte[] {1}), 1)); + assertThrows(SQLException.class, () -> ps.getParameterMetaData()); + assertThrows(SQLException.class, () -> metadata.isWrapperFor(ParameterMetaData.class)); + assertThrows(SQLException.class, () -> metadata.unwrap(ParameterMetaData.class)); + assertThrows(SQLException.class, () -> metadata.getParameterCount()); + assertThrows(SQLException.class, () -> metadata.getParameterType(1)); + + SQLException unsupportedException = + assertThrows(SQLException.class, () -> ps.setArray(1, null)); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, () -> ps.setRowId(1, null)); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + unsupportedException = assertThrows(SQLException.class, () -> ps.setObject(1, new Object())); + assertTrue(unsupportedException.getMessage().contains("statement has been closed")); + } + + @SuppressWarnings("resource") + @Test + public void testFailedServerSidePrepareClosesStatementOperation() throws Exception { + when(client.prepareStatement(any(TSPrepareReq.class))) + .thenThrow(new TException("prepare failed")); + + assertThrows( + SQLException.class, + () -> new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId)); + + verify(client).closeOperation(any()); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedExecutionClosesPreviousResultSet() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ResultSet previousResultSet = mock(ResultSet.class); + ps.resultSet = previousResultSet; + + ps.setInt(1, 1); + ps.execute(); + + verify(previousResultSet).close(); + assertNull(ps.resultSet); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedExecuteStoresQueryResultSet() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + when(execStatementResp.isSetQueryResult()).thenReturn(true); + when(execStatementResp.getColumns()).thenReturn(Collections.singletonList("s1")); + when(execStatementResp.getDataTypeList()).thenReturn(Collections.singletonList("INT32")); + when(execStatementResp.getColumnIndex2TsBlockColumnIndexList()) + .thenReturn(Collections.singletonList(0)); + execStatementResp.queryResult = Collections.emptyList(); + + ps.setInt(1, 1); + + assertTrue(ps.execute()); + + ResultSet resultSet = ps.getResultSet(); + assertNotNull(resultSet); + assertSame(resultSet, ps.getResultSet()); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedCancelCancelsActiveQueryResult() throws Exception { + long queryId = 12L; + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + when(execStatementResp.isSetQueryId()).thenReturn(true); + when(execStatementResp.getQueryId()).thenReturn(queryId); + when(execStatementResp.isSetQueryResult()).thenReturn(true); + when(execStatementResp.getColumns()).thenReturn(Collections.singletonList("s1")); + when(execStatementResp.getDataTypeList()).thenReturn(Collections.singletonList("INT32")); + when(execStatementResp.getColumnIndex2TsBlockColumnIndexList()) + .thenReturn(Collections.singletonList(0)); + when(client.cancelOperation(any(TSCancelOperationReq.class))).thenReturn(Status_SUCCESS); + execStatementResp.queryResult = Collections.emptyList(); + + ps.setInt(1, 1); + assertTrue(ps.execute()); + + ps.cancel(); + + ArgumentCaptor cancelReq = + ArgumentCaptor.forClass(TSCancelOperationReq.class); + verify(client).cancelOperation(cancelReq.capture()); + assertEquals(queryId, cancelReq.getValue().getQueryId()); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedClosesUnexpectedQueryOperationWithoutResult() throws Exception { + long statementId = 3L; + long queryId = 13L; + when(client.requestStatementId(anyLong())).thenReturn(statementId); + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + TSExecuteStatementResp resp = new TSExecuteStatementResp(); + resp.setStatus(Status_SUCCESS); + resp.setQueryId(queryId); + when(client.executePreparedStatement(any(TSExecutePreparedReq.class))).thenReturn(resp); + + ps.setInt(1, 1); + + assertFalse(ps.execute()); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(statementId, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + assertNull(ps.resultSet); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedClosesQueryOperationWhenResultSetCreationFails() throws Exception { + long queryId = 14L; + when(client.requestStatementId(anyLong())).thenReturn(4L); + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + TSExecuteStatementResp malformedResp = new TSExecuteStatementResp(); + malformedResp.setStatus(Status_SUCCESS); + malformedResp.setQueryId(queryId); + malformedResp.setColumns(Collections.singletonList("s1")); + malformedResp.setDataTypeList(Collections.emptyList()); + malformedResp.setQueryResult(Collections.emptyList()); + when(client.executePreparedStatement(any(TSExecutePreparedReq.class))) + .thenReturn(malformedResp); + + ps.setInt(1, 1); + + assertThrows(SQLException.class, ps::execute); + + ArgumentCaptor closeReq = + ArgumentCaptor.forClass(TSCloseOperationReq.class); + verify(client).closeOperation(closeReq.capture()); + assertEquals(4L, closeReq.getValue().getStatementId()); + assertEquals(queryId, closeReq.getValue().getQueryId()); + assertNull(ps.resultSet); + } + + @SuppressWarnings("resource") + @Test + public void testTablePreparedUnsetParameterClosesPreviousResultSet() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ResultSet previousResultSet = mock(ResultSet.class); + ps.resultSet = previousResultSet; + + assertThrows(SQLException.class, ps::execute); + + verify(previousResultSet).close(); + assertNull(ps.resultSet); + } + + @SuppressWarnings("resource") + @Test + public void testCloseClosesResultSetBeforeDeallocatingPreparedStatement() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ResultSet resultSet = mock(ResultSet.class); + ps.resultSet = resultSet; + + ps.close(); + + InOrder inOrder = inOrder(resultSet, client); + inOrder.verify(resultSet).close(); + inOrder.verify(client).deallocatePreparedStatement(any()); + assertTrue(ps.isClosed()); + assertNull(ps.resultSet); + } + + @SuppressWarnings("resource") + @Test + public void testCloseDeallocatesPreparedStatementWhenResultSetCloseFails() throws Exception { + IoTDBTablePreparedStatement ps = + new IoTDBTablePreparedStatement(connection, client, sessionId, "SELECT ?", zoneId); + ResultSet resultSet = mock(ResultSet.class); + doThrow(new SQLException("result set close failed")).when(resultSet).close(); + ps.resultSet = resultSet; + + SQLException closeException = assertThrows(SQLException.class, ps::close); + + assertEquals("result set close failed", closeException.getMessage()); + verify(client).deallocatePreparedStatement(any()); + verify(client).closeOperation(any()); + assertTrue(ps.isClosed()); + assertNull(ps.resultSet); + } + @SuppressWarnings("resource") @Test public void testTableModelLoginInjectionWithComment() throws Exception { @@ -237,4 +526,62 @@ public void testTableModelStringWithNull() throws Exception { 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 argument = + ArgumentCaptor.forClass(TSExecutePreparedReq.class); + verify(client).executePreparedStatement(argument.capture()); + List 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); + + assertEquals(1, ps.getParameterMetaData().getParameterCount()); + assertThrows(SQLException.class, () -> ps.setString(0, null)); + assertThrows(SQLException.class, () -> ps.setString(2, null)); + + ps.setString(1, null); + ps.execute(); + + ArgumentCaptor 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 4c401b88017af..fbc7fe4831a1e 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 @@ -30,6 +30,7 @@ import java.util.Properties; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -105,6 +106,69 @@ public void testParseDomainName() throws IoTDBURLException { assertEquals(6667, params.getPort()); } + @Test + public void testParseUrlAllowsNullProperties() throws IoTDBURLException { + IoTDBConnectionParams params = Utils.parseUrl("jdbc:iotdb://test:6667", null); + + assertEquals("test", params.getHost()); + assertEquals(6667, params.getPort()); + assertEquals(Config.DEFAULT_USER, params.getUsername()); + assertEquals(Config.DEFAULT_PASSWORD, params.getPassword()); + } + + @Test + public void testParseDefaultUrlAppliesConnectionProperties() throws IoTDBURLException { + Properties properties = new Properties(); + properties.setProperty(Config.AUTH_USER, "root"); + properties.setProperty(Config.NETWORK_TIMEOUT, "123"); + + IoTDBConnectionParams params = Utils.parseUrl(Config.IOTDB_URL_PREFIX, properties); + + assertEquals(Config.IOTDB_DEFAULT_HOST, params.getHost()); + assertEquals(Config.IOTDB_DEFAULT_PORT, params.getPort()); + assertEquals("root", params.getUsername()); + 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 = + Utils.parseUrl("jdbc:iotdb://test:6667/?use_ssl=true", new Properties()); + + assertEquals("test", params.getHost()); + assertEquals(6667, params.getPort()); + assertFalse(params.getDb().isPresent()); + assertTrue(params.isUseSSL()); + } + + @Test + public void testParseUrlAllowsColonAfterAuthority() throws IoTDBURLException { + Properties properties = new Properties(); + + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?trust_store=scheme:path", properties); + + assertEquals("scheme:path", properties.getProperty(Config.TRUST_STORE)); + } + @Test(expected = IoTDBURLException.class) public void testParseWrongUrl2() throws IoTDBURLException { Properties properties = new Properties(); @@ -123,6 +187,11 @@ public void testParseWrongUrl4() throws IoTDBURLException { Utils.parseUrl("jdbc:iotdb//6667?rpc_compress=true&aaa=bbb", properties); } + @Test(expected = IoTDBURLException.class) + public void testParseWrongUrlWithoutPort() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://test", new Properties()); + } + @Test(expected = IoTDBURLException.class) public void testParseWrongPort() throws IoTDBURLException { String userName = "test"; @@ -155,8 +224,106 @@ public void testVerifySuccess() { @Test public void testRpcCompress() throws IoTDBURLException { + boolean originalRpcCompression = Config.rpcThriftCompressionEnable; + Config.rpcThriftCompressionEnable = false; + Properties properties = new Properties(); + try { + IoTDBConnectionParams params = + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?rpc_compress=true", properties); + + assertTrue(params.isRpcThriftCompressionEnabled()); + assertFalse(Config.rpcThriftCompressionEnable); + assertFalse( + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667", new Properties()) + .isRpcThriftCompressionEnabled()); + } finally { + Config.rpcThriftCompressionEnable = originalRpcCompression; + } + } + + @Test + public void testParseUrlParamValueAllowsEqualsSign() throws IoTDBURLException { Properties properties = new Properties(); - Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?rpc_compress=true", properties); - assertTrue(Config.rpcThriftCompressionEnable); + + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?trust_store_pwd=a=b=c", properties); + + assertEquals("a=b=c", properties.getProperty(Config.TRUST_STORE_PWD)); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsEmptyValue() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?use_ssl=", new Properties()); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsTrailingSeparator() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?use_ssl=true&", new Properties()); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsInvalidBooleanValue() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?use_ssl=abc", new Properties()); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsInvalidVersionValue() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?version=bad", new Properties()); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsInvalidNetworkTimeoutValue() throws IoTDBURLException { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667?network_timeout=bad", new Properties()); + } + + @Test(expected = IoTDBURLException.class) + public void testParseUrlParamRejectsNegativeNetworkTimeoutValue() throws IoTDBURLException { + 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()); + } + + @Test + public void testParseUrlRejectsInvalidConnectionProperties() { + assertInvalidProperty(Config.DEFAULT_BUFFER_CAPACITY, "bad"); + assertInvalidProperty(Config.THRIFT_FRAME_MAX_SIZE, "bad"); + assertInvalidProperty(Config.VERSION, "bad"); + assertInvalidProperty(Config.NETWORK_TIMEOUT, "bad"); + assertInvalidProperty(Config.TIME_ZONE, "bad-time-zone"); + assertInvalidProperty(Config.CHARSET, "bad-charset"); + assertInvalidProperty(Config.USE_SSL, "bad"); + assertInvalidProperty(Config.SQL_DIALECT, "bad"); + } + + @Test + public void testParseUrlRejectsInvalidIntegerRanges() { + assertInvalidProperty(Config.DEFAULT_BUFFER_CAPACITY, "0"); + assertInvalidProperty(Config.THRIFT_FRAME_MAX_SIZE, "-1"); + assertInvalidProperty(Config.NETWORK_TIMEOUT, "-1"); + } + + private static void assertInvalidProperty(String key, String value) { + Properties properties = new Properties(); + properties.setProperty(key, value); + try { + Utils.parseUrl("jdbc:iotdb://127.0.0.1:6667", properties); + } catch (IoTDBURLException e) { + assertTrue(e.getMessage().contains(key)); + return; + } + fail("Expected IoTDBURLException for invalid property " + key); } } 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 42bf974a3eb80..75d4e0a475494 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 @@ -54,6 +54,7 @@ public class IoTDBRpcDataSet { private final String sql; private boolean isClosed = false; + private boolean explicitlyClosed = false; private IClientRPCService.Iface client; private final List columnNameList; // no deduplication private final List columnTypeList; // no deduplication @@ -207,7 +208,12 @@ public IoTDBRpcDataSet( } public void close() throws StatementExecutionException, TException { + close(true); + } + + private void close(boolean explicitlyClosed) throws StatementExecutionException, TException { if (isClosed) { + this.explicitlyClosed = this.explicitlyClosed || explicitlyClosed; return; } if (client != null) { @@ -225,9 +231,13 @@ public void close() throws StatementExecutionException, TException { } client = null; isClosed = true; + this.explicitlyClosed = explicitlyClosed; } public boolean next() throws StatementExecutionException, IoTDBConnectionException { + if (explicitlyClosed) { + throw new IoTDBConnectionException(RpcMessages.DATASET_ALREADY_CLOSED); + } if (hasCachedBlock()) { lastReadWasNull = false; constructOneRow(); @@ -245,7 +255,7 @@ public boolean next() throws StatementExecutionException, IoTDBConnectionExcepti return true; } else { try { - close(); + close(false); return false; } catch (TException e) { throw new IoTDBConnectionException(RpcMessages.CANNOT_CLOSE_DATASET, e); @@ -265,7 +275,7 @@ public boolean fetchResults() throws StatementExecutionException, IoTDBConnectio RpcUtils.verifySuccess(resp.getStatus()); moreData = resp.moreData; if (!resp.hasResultSet) { - close(); + close(false); } else { queryResult = resp.getQueryResult(); queryResultIndex = 0; @@ -640,6 +650,9 @@ private int getTsBlockColumnIndexForColumnIndex(int columnIndex) { } public void checkRecord() throws StatementExecutionException { + if (explicitlyClosed) { + throw new StatementExecutionException(RpcMessages.DATASET_ALREADY_CLOSED); + } if (queryResultIndex > queryResultSize || tsBlockIndex >= tsBlockSize || queryResult == null