Skip to content

Commit b9ac3e7

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. 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. Fixed non-conforming methods to throw SQLException as per standard. Closes tarantool#38
1 parent 8aad007 commit b9ac3e7

12 files changed

+1101
-120
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

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

3+
import java.io.IOException;
34
import java.sql.Array;
45
import java.sql.Blob;
56
import java.sql.CallableStatement;
@@ -20,6 +21,7 @@
2021
import java.util.Properties;
2122
import java.util.concurrent.Executor;
2223

24+
import org.tarantool.CommunicationException;
2325
import org.tarantool.TarantoolConnection;
2426

2527
@SuppressWarnings("Since15")
@@ -36,12 +38,14 @@ public SQLConnection(TarantoolConnection connection, String url, Properties prop
3638

3739
@Override
3840
public Statement createStatement() throws SQLException {
39-
return new SQLStatement(connection, this);
41+
checkNotClosed();
42+
return new SQLStatement(this);
4043
}
4144

4245
@Override
4346
public PreparedStatement prepareStatement(String sql) throws SQLException {
44-
return new SQLPreparedStatement(connection, this, sql);
47+
checkNotClosed();
48+
return new SQLPreparedStatement(this, sql);
4549
}
4650

4751
@Override
@@ -89,6 +93,7 @@ public boolean isClosed() throws SQLException {
8993

9094
@Override
9195
public DatabaseMetaData getMetaData() throws SQLException {
96+
checkNotClosed();
9297
return new SQLDatabaseMetadata(this);
9398
}
9499

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

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

299313
@Override
300314
public int getNetworkTimeout() throws SQLException {
301-
throw new SQLFeatureNotSupportedException();
315+
checkNotClosed();
316+
try {
317+
return connection.getSocketTimeout();
318+
} catch (Exception e) {
319+
throw new SQLException("Failed to retrieve socket timeout", e);
320+
}
302321
}
303322

304-
305323
@Override
306324
public <T> T unwrap(Class<T> iface) throws SQLException {
307325
throw new SQLFeatureNotSupportedException();
@@ -311,4 +329,28 @@ public <T> T unwrap(Class<T> iface) throws SQLException {
311329
public boolean isWrapperFor(Class<?> iface) throws SQLException {
312330
throw new SQLFeatureNotSupportedException();
313331
}
332+
333+
/**
334+
* @throws SQLException If connection is closed.
335+
*/
336+
protected void checkNotClosed() throws SQLException {
337+
if (isClosed())
338+
throw new SQLException("Connection is closed.");
339+
}
340+
341+
/**
342+
* Inspects passed exception and closes the connection if appropriate.
343+
*
344+
* @param e Exception to process.
345+
*/
346+
protected void handleException(Exception e) {
347+
if (CommunicationException.class.isAssignableFrom(e.getClass()) ||
348+
IOException.class.isAssignableFrom(e.getClass())) {
349+
try {
350+
close();
351+
} catch (Exception ex) {
352+
e.addSuppressed(ex);
353+
}
354+
}
355+
}
314356
}

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

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

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

735-
return new SQLNullResultSet((JDBCBridge.mock(
736-
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",
737-
//nulls
738-
"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"
739-
),
740-
rows)));
744+
return new SQLNullResultSet((JDBCBridge.mock(
745+
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",
746+
//nulls
747+
"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"
748+
),
749+
rows)));
750+
} catch (Exception e) {
751+
connection.handleException(e);
752+
throw new SQLException(
753+
"Failed to retrieve column(s) description: " +
754+
"tableNamePattern=" + tableNamePattern +
755+
"columnNamePattern=" + columnNamePattern, e);
756+
}
741757
}
742758

743759
@Override
@@ -765,29 +781,35 @@ public ResultSet getVersionColumns(String catalog, String schema, String table)
765781

766782
@Override
767783
public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
768-
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 2, Arrays.asList(table), 0, 1, 0);
769-
770-
List<List<Object>> rows = new ArrayList<List<Object>>();
771-
772-
if (spaces != null && spaces.size() > 0) {
773-
List<Object> space = spaces.get(0);
774-
List<Map<String, Object>> fields = (List<Map<String, Object>>) space.get(FORMAT_IDX);
775-
List<List<Object>> indexes = (List<List<Object>>) connection.connection.select(_VINDEX, 0, Arrays.asList(space.get(SPACE_ID_IDX), 0), 0, 1, 0);
776-
List<Object> primaryKey = indexes.get(0);
777-
List<List<Object>> parts = (List<List<Object>>) primaryKey.get(INDEX_FORMAT_IDX);
778-
for (int i = 0; i < parts.size(); i++) {
779-
List<Object> part = parts.get(i);
780-
int ordinal = ((Number) part.get(0)).intValue();
781-
String column = (String) fields.get(ordinal).get("name");
782-
rows.add(Arrays.asList(table, column, i + 1, "primary", primaryKey.get(NAME_IDX)));
784+
connection.checkNotClosed();
785+
try {
786+
List<List<Object>> spaces = (List<List<Object>>) connection.connection.select(_VSPACE, 2, Arrays.asList(table), 0, 1, 0);
787+
788+
List<List<Object>> rows = new ArrayList<List<Object>>();
789+
790+
if (spaces != null && spaces.size() > 0) {
791+
List<Object> space = spaces.get(0);
792+
List<Map<String, Object>> fields = (List<Map<String, Object>>) space.get(FORMAT_IDX);
793+
List<List<Object>> indexes = (List<List<Object>>) connection.connection.select(_VINDEX, 0, Arrays.asList(space.get(SPACE_ID_IDX), 0), 0, 1, 0);
794+
List<Object> primaryKey = indexes.get(0);
795+
List<List<Object>> parts = (List<List<Object>>) primaryKey.get(INDEX_FORMAT_IDX);
796+
for (int i = 0; i < parts.size(); i++) {
797+
List<Object> part = parts.get(i);
798+
int ordinal = ((Number) part.get(0)).intValue();
799+
String column = (String) fields.get(ordinal).get("name");
800+
rows.add(Arrays.asList(table, column, i + 1, "primary", primaryKey.get(NAME_IDX)));
801+
}
783802
}
803+
return new SQLNullResultSet((JDBCBridge.mock(
804+
Arrays.asList("TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME",
805+
//nulls
806+
"TABLE_CAT", "TABLE_SCHEM"
807+
),
808+
rows)));
809+
} catch (Exception e) {
810+
connection.handleException(e);
811+
throw new SQLException("Failed to retrieve primary key(s) description: table=" + table, e);
784812
}
785-
return new SQLNullResultSet((JDBCBridge.mock(
786-
Arrays.asList("TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME",
787-
//nulls
788-
"TABLE_CAT", "TABLE_SCHEM"
789-
),
790-
rows)));
791813
}
792814

793815
@Override

0 commit comments

Comments
 (0)