Skip to content

Commit e696435

Browse files
committed
jdbc: add connection timeout configuration and handling
Added connection property `socketTimeout` to allow user control over network timeout before actual connection is returned by the driver. This is only done for default socket provider. The default timeout is is left to be infinite. Implemented `Connection.setNetworkTimeout` API to make it possible to change the maximum amount of time to wait for server replies after the connection is established. The connection that has timed out is forced to close. New subsequent operations requested on such connection must fail right away. The corresponding checks are embedded into relevant APIs. Closes tarantool#38
1 parent 06b7ffb commit e696435

12 files changed

+1036
-103
lines changed

src/main/java/org/tarantool/TarantoolConnection.java

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.InputStream;
55
import java.io.OutputStream;
66
import java.net.Socket;
7+
import java.net.SocketException;
78
import java.nio.ByteBuffer;
89
import java.util.List;
910
import java.util.Map;
@@ -80,4 +81,24 @@ protected void sql(String sql, Object[] bind) {
8081
public boolean isClosed() {
8182
return socket.isClosed();
8283
}
84+
85+
/**
86+
* Sets given timeout value on underlying socket.
87+
*
88+
* @param timeout Timeout in milliseconds.
89+
* @throws SocketException If failed.
90+
*/
91+
public void setSocketTimeout(int timeout) throws SocketException {
92+
socket.setSoTimeout(timeout);
93+
}
94+
95+
/**
96+
* Retrieves timeout value from underlying socket.
97+
*
98+
* @return Timeout in milliseconds.
99+
* @throws SocketException If failed.
100+
*/
101+
public int getSocketTimeout() throws SocketException {
102+
return socket.getSoTimeout();
103+
}
83104
}

src/main/java/org/tarantool/jdbc/SQLConnection.java

+48-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool.jdbc;
22

3+
import java.io.IOException;
4+
import java.net.SocketException;
35
import java.sql.Array;
46
import java.sql.Blob;
57
import java.sql.CallableStatement;
@@ -20,6 +22,7 @@
2022
import java.util.Properties;
2123
import java.util.concurrent.Executor;
2224

25+
import org.tarantool.CommunicationException;
2326
import org.tarantool.TarantoolConnection;
2427

2528
@SuppressWarnings("Since15")
@@ -36,12 +39,14 @@ public SQLConnection(TarantoolConnection connection, String url, Properties prop
3639

3740
@Override
3841
public Statement createStatement() throws SQLException {
39-
return new SQLStatement(connection, this);
42+
checkNotClosed();
43+
return new SQLStatement(this);
4044
}
4145

4246
@Override
4347
public PreparedStatement prepareStatement(String sql) throws SQLException {
44-
return new SQLPreparedStatement(connection, this, sql);
48+
checkNotClosed();
49+
return new SQLPreparedStatement(this, sql);
4550
}
4651

4752
@Override
@@ -89,6 +94,7 @@ public boolean isClosed() throws SQLException {
8994

9095
@Override
9196
public DatabaseMetaData getMetaData() throws SQLException {
97+
checkNotClosed();
9298
return new SQLDatabaseMetadata(this);
9399
}
94100

@@ -293,15 +299,28 @@ public void abort(Executor executor) throws SQLException {
293299

294300
@Override
295301
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
296-
throw new SQLFeatureNotSupportedException();
302+
checkNotClosed();
303+
304+
if (milliseconds < 0)
305+
throw new SQLException("Network timeout cannot be negative.");
306+
307+
try {
308+
connection.setSocketTimeout(milliseconds);
309+
} catch (SocketException e) {
310+
throw new SQLException("Failed to set socket timeout: timeout=" + milliseconds, e);
311+
}
297312
}
298313

299314
@Override
300315
public int getNetworkTimeout() throws SQLException {
301-
throw new SQLFeatureNotSupportedException();
316+
checkNotClosed();
317+
try {
318+
return connection.getSocketTimeout();
319+
} catch (SocketException e) {
320+
throw new SQLException("Failed to retrieve socket timeout", e);
321+
}
302322
}
303323

304-
305324
@Override
306325
public <T> T unwrap(Class<T> iface) throws SQLException {
307326
throw new SQLFeatureNotSupportedException();
@@ -311,4 +330,28 @@ public <T> T unwrap(Class<T> iface) throws SQLException {
311330
public boolean isWrapperFor(Class<?> iface) throws SQLException {
312331
throw new SQLFeatureNotSupportedException();
313332
}
333+
334+
/**
335+
* @throws SQLException If connection is closed.
336+
*/
337+
protected void checkNotClosed() throws SQLException {
338+
if (isClosed())
339+
throw new SQLException("Connection is closed.");
340+
}
341+
342+
/**
343+
* Inspects passed exception and closes the connection if appropriate.
344+
*
345+
* @param e Exception to process.
346+
*/
347+
protected void handleException(Exception e) {
348+
if (CommunicationException.class.isAssignableFrom(e.getClass()) ||
349+
IOException.class.isAssignableFrom(e.getClass())) {
350+
try {
351+
close();
352+
} catch (SQLException ignored) {
353+
// No-op.
354+
}
355+
}
356+
}
314357
}

src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java

+57-40
Original file line numberDiff line numberDiff line change
@@ -672,23 +672,30 @@ protected boolean like(String value, String[] parts) {
672672

673673
@Override
674674
public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types)
675-
throws SQLException {
676-
if (types != null && !Arrays.asList(types).contains("TABLE")) {
677-
return new SQLResultSet(JDBCBridge.EMPTY);
678-
}
679-
String[] parts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
680-
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
681-
List<List<Object>> rows = new ArrayList<List<Object>>();
682-
for (List<Object> space : spaces) {
683-
String name = (String) space.get(NAME_IDX);
684-
Map flags = (Map) space.get(FLAGS_IDX);
685-
if (flags != null && flags.containsKey("sql") && like(name, parts)) {
686-
rows.add(Arrays.asList(name, "TABLE", flags.get("sql")));
675+
throws SQLException {
676+
connection.checkNotClosed();
677+
try {
678+
if (types != null && !Arrays.asList(types).contains("TABLE")) {
679+
return new SQLResultSet(JDBCBridge.EMPTY);
680+
}
681+
String[] parts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
682+
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
683+
List<List<Object>> rows = new ArrayList<List<Object>>();
684+
for (List<Object> space : spaces) {
685+
String name = (String) space.get(NAME_IDX);
686+
Map flags = (Map) space.get(FLAGS_IDX);
687+
if (flags != null && flags.containsKey("sql") && like(name, parts)) {
688+
rows.add(Arrays.asList(name, "TABLE", flags.get("sql")));
689+
}
687690
}
691+
return new SQLNullResultSet(JDBCBridge.mock(Arrays.asList("TABLE_NAME", "TABLE_TYPE", "REMARKS",
692+
//nulls
693+
"TABLE_CAT", "TABLE_SCHEM", "TABLE_TYPE", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"), rows));
694+
} catch (Exception e) {
695+
connection.handleException(e);
696+
throw new SQLException("Failed to retrieve table(s) description: " +
697+
"tableNamePattern=\"" + tableNamePattern + "\".", e);
688698
}
689-
return new SQLNullResultSet(JDBCBridge.mock(Arrays.asList("TABLE_NAME", "TABLE_TYPE", "REMARKS",
690-
//nulls
691-
"TABLE_CAT", "TABLE_SCHEM", "TABLE_TYPE", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"), rows));
692699
}
693700

694701
@Override
@@ -713,32 +720,40 @@ public ResultSet getTableTypes() throws SQLException {
713720
@Override
714721
public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
715722
throws SQLException {
716-
String[] tableParts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
717-
String[] colParts = columnNamePattern == null ? new String[]{""} : columnNamePattern.split("%");
718-
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
719-
List<List<Object>> rows = new ArrayList<List<Object>>();
720-
for (List<Object> space : spaces) {
721-
String tableName = (String) space.get(NAME_IDX);
722-
Map flags = (Map) space.get(FLAGS_IDX);
723-
if (flags != null && flags.containsKey("sql") && like(tableName, tableParts)) {
724-
List<Map<String, Object>> format = (List<Map<String, Object>>) space.get(FORMAT_IDX);
725-
for (int columnIdx = 1; columnIdx <= format.size(); columnIdx++) {
726-
Map<String, Object> f = format.get(columnIdx - 1);
727-
String columnName = (String) f.get("name");
728-
String dbType = (String) f.get("type");
729-
if (like(columnName, colParts)) {
730-
rows.add(Arrays.<Object>asList(tableName, columnName, columnIdx, Types.OTHER, dbType, 10, 1, "YES", Types.OTHER, "NO", "NO"));
723+
connection.checkNotClosed();
724+
try {
725+
String[] tableParts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
726+
String[] colParts = columnNamePattern == null ? new String[]{""} : columnNamePattern.split("%");
727+
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
728+
List<List<Object>> rows = new ArrayList<List<Object>>();
729+
for (List<Object> space : spaces) {
730+
String tableName = (String) space.get(NAME_IDX);
731+
Map flags = (Map) space.get(FLAGS_IDX);
732+
if (flags != null && flags.containsKey("sql") && like(tableName, tableParts)) {
733+
List<Map<String, Object>> format = (List<Map<String, Object>>) space.get(FORMAT_IDX);
734+
for (int columnIdx = 1; columnIdx <= format.size(); columnIdx++) {
735+
Map<String, Object> f = format.get(columnIdx - 1);
736+
String columnName = (String) f.get("name");
737+
String dbType = (String) f.get("type");
738+
if (like(columnName, colParts)) {
739+
rows.add(Arrays.<Object>asList(tableName, columnName, columnIdx, Types.OTHER, dbType, 10, 1, "YES", Types.OTHER, "NO", "NO"));
740+
}
731741
}
732742
}
733743
}
734-
}
735744

736-
return new SQLNullResultSet((JDBCBridge.mock(
737-
Arrays.asList("TABLE_NAME", "COLUMN_NAME", "ORDINAL_POSITION", "DATA_TYPE", "TYPE_NAME", "NUM_PREC_RADIX", "NULLABLE", "IS_NULLABLE", "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT", "IS_GENERATEDCOLUMN",
738-
//nulls
739-
"TABLE_CAT", "TABLE_SCHEM", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "REMARKS", "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "SCOPE_CATALOG", "SCOPE_SCHEMA", "SCOPE_TABLE"
740-
),
741-
rows)));
745+
return new SQLNullResultSet((JDBCBridge.mock(
746+
Arrays.asList("TABLE_NAME", "COLUMN_NAME", "ORDINAL_POSITION", "DATA_TYPE", "TYPE_NAME", "NUM_PREC_RADIX", "NULLABLE", "IS_NULLABLE", "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT", "IS_GENERATEDCOLUMN",
747+
//nulls
748+
"TABLE_CAT", "TABLE_SCHEM", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "REMARKS", "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "SCOPE_CATALOG", "SCOPE_SCHEMA", "SCOPE_TABLE"
749+
),
750+
rows)));
751+
} catch (Exception e) {
752+
connection.handleException(e);
753+
throw new SQLException("Error processing table column metadata: " +
754+
"tableNamePattern=\"" + tableNamePattern + "\"; " +
755+
"columnNamePattern=\"" + columnNamePattern + "\".", e);
756+
}
742757
}
743758

744759
@Override
@@ -766,6 +781,8 @@ public ResultSet getVersionColumns(String catalog, String schema, String table)
766781

767782
@Override
768783
public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
784+
connection.checkNotClosed();
785+
769786
final List<String> colNames = Arrays.asList(
770787
"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME");
771788

@@ -809,9 +826,9 @@ public int compare(List<Object> row0, List<Object> row1) {
809826
}
810827
});
811828
return new SQLNullResultSet((JDBCBridge.mock(colNames, rows)));
812-
}
813-
catch (Throwable t) {
814-
throw new SQLException("Error processing metadata for table \"" + table + "\".", t);
829+
} catch (Exception e) {
830+
connection.handleException(e);
831+
throw new SQLException("Error processing metadata for table \"" + table + "\".", e);
815832
}
816833
}
817834

0 commit comments

Comments
 (0)