Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 268eafa

Browse files
dponomarevnicktorwald
dponomarev
authored andcommittedMar 19, 2019
jdbc: add support for a result set holdability
Right now we only can support HOLD_CURSORS_OVER_COMMIT due to lack of Tarantool transaction support and, possibly, cursors. So, in terms of HOLD_CURSORS_OVER_COMMIT we always load a result set completely and it can be used as long as it is opened. Implement the holdability support (next HS) for SQLDatabaseMetaData in part of getting a default holdability and checking proper support. Implement HS for SQLConnection in part of producing new statements and prepared statements (but excluding CallableStatements due to lack of implementation). Implement HS for SQL(Prepared)Statement as well as for SQLResultSet which is produced by the statements. Some part of this feature requires the implementation of java.sql.Wrapper for SQLConnection and SQL(Prepared)Statement. So now they fully implement the interface. Add missed checks for an open status of JDBC components which are required by the specification. Some methods start returning appropriate SQLException subclasses when corresponding errors occur. Add SQLStates enumeration to help to produce the errors with the standard SQL states. Finally, JDBCBridge is no longer aware of the SQLResultSet class. Only Statement implementations are responsible for building of new result sets according to the specification design. Next plan is to completely avoid JDBCBridge logic and transfer it to an inner helper inside the Statement implementations. Closes: #87 Affects: #73, #119
1 parent 9b3f91f commit 268eafa

12 files changed

+544
-187
lines changed
 

‎src/main/java/org/tarantool/JDBCBridge.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.tarantool.protocol.TarantoolPacket;
1212

1313
public class JDBCBridge {
14+
1415
public static final JDBCBridge EMPTY = new JDBCBridge(Collections.<TarantoolBase.SQLMetaData>emptyList(), Collections.<List<Object>>emptyList());
1516

1617
final List<TarantoolBase.SQLMetaData> sqlMetadata;
@@ -41,7 +42,7 @@ public static int update(TarantoolConnection connection, String sql, Object ...
4142

4243
public static JDBCBridge mock(List<String> fields, List<List<Object>> values) {
4344
List<TarantoolBase.SQLMetaData> meta = new ArrayList<TarantoolBase.SQLMetaData>(fields.size());
44-
for(String field:fields) {
45+
for(String field : fields) {
4546
meta.add(new TarantoolBase.SQLMetaData(field));
4647
}
4748
return new JDBCBridge(meta, values);
@@ -51,7 +52,7 @@ public static Object execute(TarantoolConnection connection, String sql, Object
5152
TarantoolPacket pack = connection.sql(sql, params);
5253
Long rowCount = SqlProtoUtils.getSqlRowCount(pack);
5354
if(rowCount == null) {
54-
return new SQLResultSet(new JDBCBridge(pack));
55+
return new JDBCBridge(pack);
5556
}
5657
return rowCount.intValue();
5758
}

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

Lines changed: 97 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package org.tarantool.jdbc;
22

3+
import org.tarantool.CommunicationException;
4+
import org.tarantool.JDBCBridge;
5+
import org.tarantool.TarantoolConnection;
6+
import org.tarantool.util.SQLStates;
7+
38
import java.io.IOException;
49
import java.net.InetSocketAddress;
510
import java.net.Socket;
@@ -16,6 +21,8 @@
1621
import java.sql.SQLClientInfoException;
1722
import java.sql.SQLException;
1823
import java.sql.SQLFeatureNotSupportedException;
24+
import java.sql.SQLNonTransientConnectionException;
25+
import java.sql.SQLNonTransientException;
1926
import java.sql.SQLWarning;
2027
import java.sql.SQLXML;
2128
import java.sql.Savepoint;
@@ -27,10 +34,6 @@
2734
import java.util.Properties;
2835
import java.util.concurrent.Executor;
2936

30-
import org.tarantool.CommunicationException;
31-
import org.tarantool.JDBCBridge;
32-
import org.tarantool.TarantoolConnection;
33-
3437
import static org.tarantool.jdbc.SQLDriver.PROP_HOST;
3538
import static org.tarantool.jdbc.SQLDriver.PROP_PASSWORD;
3639
import static org.tarantool.jdbc.SQLDriver.PROP_PORT;
@@ -39,9 +42,17 @@
3942

4043
@SuppressWarnings("Since15")
4144
public class SQLConnection implements Connection {
45+
46+
private static final int UNSET_HOLDABILITY = 0;
47+
4248
private final TarantoolConnection connection;
43-
final String url;
44-
final Properties properties;
49+
50+
private final String url;
51+
private final Properties properties;
52+
53+
private DatabaseMetaData cachedMetadata;
54+
55+
private int resultSetHoldability = UNSET_HOLDABILITY;
4556

4657
SQLConnection(String url, Properties properties) throws SQLException {
4758
this.url = url;
@@ -62,20 +73,20 @@ public class SQLConnection implements Connection {
6273
}
6374
}
6475
if (e instanceof SQLException)
65-
throw (SQLException)e;
76+
throw (SQLException) e;
6677
throw new SQLException("Couldn't initiate connection using " + SQLDriver.diagProperties(properties), e);
6778
}
6879
}
6980

7081
/**
7182
* Provides a connected socket to be used to initialize a native tarantool
7283
* connection.
73-
*
84+
* <p>
7485
* The implementation assumes that {@link #properties} contains all the
7586
* necessary info extracted from both the URI and connection properties
7687
* provided by the user. However, the overrides are free to also use the
7788
* {@link #url} if required.
78-
*
89+
* <p>
7990
* A connect is guarded with user provided timeout. Socket is configured
8091
* to honor this timeout for the following read/write operations as well.
8192
*
@@ -111,7 +122,7 @@ protected Socket getConnectedSocket() throws SQLException {
111122
/**
112123
* Provides a newly connected socket instance. The method is intended to be
113124
* overridden to enable unit testing of the class.
114-
*
125+
* <p>
115126
* Not supposed to contain any logic other than a call to constructor.
116127
*
117128
* @return socket.
@@ -123,11 +134,11 @@ protected Socket makeSocket() {
123134
/**
124135
* Provides a native tarantool connection instance. The method is intended
125136
* to be overridden to enable unit testing of the class.
126-
*
137+
* <p>
127138
* Not supposed to contain any logic other than a call to constructor.
128139
*
129-
* @param user User name.
130-
* @param pass Password.
140+
* @param user User name.
141+
* @param pass Password.
131142
* @param socket Connected socket.
132143
* @return Native tarantool connection.
133144
* @throws IOException if failed.
@@ -140,14 +151,12 @@ protected TarantoolConnection makeConnection(String user, String pass, Socket so
140151

141152
@Override
142153
public Statement createStatement() throws SQLException {
143-
checkNotClosed();
144-
return new SQLStatement(this);
154+
return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
145155
}
146156

147157
@Override
148158
public PreparedStatement prepareStatement(String sql) throws SQLException {
149-
checkNotClosed();
150-
return new SQLPreparedStatement(this, sql);
159+
return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
151160
}
152161

153162
@Override
@@ -196,7 +205,10 @@ public boolean isClosed() throws SQLException {
196205
@Override
197206
public DatabaseMetaData getMetaData() throws SQLException {
198207
checkNotClosed();
199-
return new SQLDatabaseMetadata(this);
208+
if (cachedMetadata == null) {
209+
cachedMetadata = new SQLDatabaseMetadata(this);
210+
}
211+
return cachedMetadata;
200212
}
201213

202214
@Override
@@ -242,13 +254,13 @@ public void clearWarnings() throws SQLException {
242254

243255
@Override
244256
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
245-
throw new SQLFeatureNotSupportedException();
257+
return createStatement(resultSetType, resultSetConcurrency, getHoldability());
246258
}
247259

248260
@Override
249261
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
250262
throws SQLException {
251-
throw new SQLFeatureNotSupportedException();
263+
return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
252264
}
253265

254266
@Override
@@ -268,12 +280,18 @@ public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
268280

269281
@Override
270282
public void setHoldability(int holdability) throws SQLException {
271-
throw new SQLFeatureNotSupportedException();
283+
checkNotClosed();
284+
checkHoldabilitySupport(holdability);
285+
resultSetHoldability = holdability;
272286
}
273287

274288
@Override
275289
public int getHoldability() throws SQLException {
276-
throw new SQLFeatureNotSupportedException();
290+
checkNotClosed();
291+
if (resultSetHoldability == UNSET_HOLDABILITY) {
292+
resultSetHoldability = getMetaData().getResultSetHoldability();
293+
}
294+
return resultSetHoldability;
277295
}
278296

279297
@Override
@@ -297,15 +315,22 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException {
297315
}
298316

299317
@Override
300-
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
301-
throws SQLException {
302-
throw new SQLFeatureNotSupportedException();
318+
public Statement createStatement(int resultSetType,
319+
int resultSetConcurrency,
320+
int resultSetHoldability) throws SQLException {
321+
checkNotClosed();
322+
checkHoldabilitySupport(resultSetHoldability);
323+
return new SQLStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
303324
}
304325

305326
@Override
306-
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
307-
throws SQLException {
308-
throw new SQLFeatureNotSupportedException();
327+
public PreparedStatement prepareStatement(String sql,
328+
int resultSetType,
329+
int resultSetConcurrency,
330+
int resultSetHoldability) throws SQLException {
331+
checkNotClosed();
332+
checkHoldabilitySupport(resultSetHoldability);
333+
return new SQLPreparedStatement(this, sql, resultSetType, resultSetConcurrency, resultSetHoldability);
309334
}
310335

311336
@Override
@@ -423,16 +448,19 @@ public int getNetworkTimeout() throws SQLException {
423448
}
424449

425450
@Override
426-
public <T> T unwrap(Class<T> iface) throws SQLException {
427-
throw new SQLFeatureNotSupportedException();
451+
public <T> T unwrap(Class<T> type) throws SQLException {
452+
if (isWrapperFor(type)) {
453+
return type.cast(this);
454+
}
455+
throw new SQLNonTransientException("Connection does not wrap " + type.getName());
428456
}
429457

430458
@Override
431-
public boolean isWrapperFor(Class<?> iface) throws SQLException {
432-
throw new SQLFeatureNotSupportedException();
459+
public boolean isWrapperFor(Class<?> type) throws SQLException {
460+
return type.isAssignableFrom(this.getClass());
433461
}
434462

435-
protected Object execute(String sql, Object ... args) throws SQLException {
463+
protected Object execute(String sql, Object... args) throws SQLException {
436464
checkNotClosed();
437465
try {
438466
return JDBCBridge.execute(connection, sql, args);
@@ -442,17 +470,17 @@ protected Object execute(String sql, Object ... args) throws SQLException {
442470
}
443471
}
444472

445-
protected ResultSet executeQuery(String sql, Object ... args) throws SQLException {
473+
protected JDBCBridge executeQuery(String sql, Object... args) throws SQLException {
446474
checkNotClosed();
447475
try {
448-
return new SQLResultSet(JDBCBridge.query(connection, sql, args));
476+
return JDBCBridge.query(connection, sql, args);
449477
} catch (Exception e) {
450478
handleException(e);
451479
throw new SQLException(formatError(sql, args), e);
452480
}
453481
}
454482

455-
protected int executeUpdate(String sql, Object ... args) throws SQLException {
483+
protected int executeUpdate(String sql, Object... args) throws SQLException {
456484
checkNotClosed();
457485
try {
458486
return JDBCBridge.update(connection, sql, args);
@@ -463,7 +491,7 @@ protected int executeUpdate(String sql, Object ... args) throws SQLException {
463491
}
464492

465493
protected List<?> nativeSelect(Integer space, Integer index, List<?> key, int offset, int limit, int iterator)
466-
throws SQLException {
494+
throws SQLException {
467495
checkNotClosed();
468496
try {
469497
return connection.select(space, index, key, offset, limit, iterator);
@@ -482,7 +510,18 @@ protected String getServerVersion() {
482510
*/
483511
protected void checkNotClosed() throws SQLException {
484512
if (isClosed())
485-
throw new SQLException("Connection is closed.");
513+
throw new SQLNonTransientConnectionException(
514+
"Connection is closed.",
515+
SQLStates.CONNECTION_DOES_NOT_EXIST.getSqlState()
516+
);
517+
}
518+
519+
String getUrl() {
520+
return url;
521+
}
522+
523+
Properties getProperties() {
524+
return properties;
486525
}
487526

488527
/**
@@ -492,7 +531,7 @@ protected void checkNotClosed() throws SQLException {
492531
*/
493532
private void handleException(Exception e) {
494533
if (CommunicationException.class.isAssignableFrom(e.getClass()) ||
495-
IOException.class.isAssignableFrom(e.getClass())) {
534+
IOException.class.isAssignableFrom(e.getClass())) {
496535
try {
497536
close();
498537
} catch (SQLException ignored) {
@@ -501,14 +540,31 @@ private void handleException(Exception e) {
501540
}
502541
}
503542

543+
/**
544+
* Checks whether <code>holdability</code> is supported
545+
*
546+
* @param holdability param to be checked
547+
* @throws SQLFeatureNotSupportedException param is not supported
548+
* @throws SQLNonTransientException param has invalid value
549+
*/
550+
private void checkHoldabilitySupport(int holdability) throws SQLException {
551+
if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT
552+
&& holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
553+
throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
554+
}
555+
if (!getMetaData().supportsResultSetHoldability(holdability)) {
556+
throw new SQLFeatureNotSupportedException();
557+
}
558+
}
559+
504560
/**
505561
* Provides error message that contains parameters of failed SQL statement.
506562
*
507-
* @param sql SQL Text.
563+
* @param sql SQL Text.
508564
* @param params Parameters of the SQL statement.
509565
* @return Formatted error message.
510566
*/
511-
private static String formatError(String sql, Object ... params) {
567+
private static String formatError(String sql, Object... params) {
512568
return "Failed to execute SQL: " + sql + ", params: " + Arrays.deepToString(params);
513569
}
514570
}

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

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.tarantool.jdbc;
22

3+
import org.tarantool.JDBCBridge;
4+
import org.tarantool.Version;
5+
36
import java.sql.Connection;
47
import java.sql.DatabaseMetaData;
58
import java.sql.ResultSet;
@@ -14,11 +17,9 @@
1417
import java.util.List;
1518
import java.util.Map;
1619

17-
import org.tarantool.Version;
18-
import org.tarantool.JDBCBridge;
19-
2020
@SuppressWarnings("Since15")
2121
public class SQLDatabaseMetadata implements DatabaseMetaData {
22+
2223
protected static final int _VSPACE = 281;
2324
protected static final int _VINDEX = 289;
2425
protected static final int SPACES_MAX = 65535;
@@ -28,16 +29,15 @@ public class SQLDatabaseMetadata implements DatabaseMetaData {
2829
public static final int SPACE_ID_IDX = 0;
2930
protected final SQLConnection connection;
3031

31-
3232
protected class SQLNullResultSet extends SQLResultSet {
3333

34-
public SQLNullResultSet(JDBCBridge bridge) {
35-
super(bridge);
34+
public SQLNullResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws SQLException {
35+
super(bridge, ownerStatement);
3636
}
3737

3838
@Override
3939
protected Object getRaw(int columnIndex) {
40-
return columnIndex > row.size() ? null : row.get(columnIndex - 1);
40+
return columnIndex > getCurrentRow().size() ? null : getCurrentRow().get(columnIndex - 1);
4141
}
4242

4343
@Override
@@ -65,12 +65,12 @@ public boolean allTablesAreSelectable() throws SQLException {
6565

6666
@Override
6767
public String getURL() throws SQLException {
68-
return connection.url;
68+
return connection.getUrl();
6969
}
7070

7171
@Override
7272
public String getUserName() throws SQLException {
73-
return connection.properties.getProperty("user");
73+
return connection.getProperties().getProperty("user");
7474
}
7575

7676
@Override
@@ -646,13 +646,13 @@ public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
646646
@Override
647647
public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern)
648648
throws SQLException {
649-
return new SQLResultSet(JDBCBridge.EMPTY);
649+
return asMetadataResultSet(JDBCBridge.EMPTY);
650650
}
651651

652652
@Override
653653
public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern)
654654
throws SQLException {
655-
return new SQLResultSet(JDBCBridge.EMPTY);
655+
return asMetadataResultSet(JDBCBridge.EMPTY);
656656
}
657657

658658
protected boolean like(String value, String[] parts) {
@@ -676,7 +676,7 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
676676
try {
677677
if (types != null && !Arrays.asList(types).contains("TABLE")) {
678678
connection.checkNotClosed();
679-
return new SQLResultSet(JDBCBridge.EMPTY);
679+
return asMetadataResultSet(JDBCBridge.EMPTY);
680680
}
681681
String[] parts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
682682
List<List<Object>> spaces = (List<List<Object>>) connection.nativeSelect(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
@@ -692,10 +692,16 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
692692
rows.add(Arrays.asList((Object) tableName, (Object) "TABLE"));
693693
}
694694
}
695-
return new SQLNullResultSet(JDBCBridge.mock(Arrays.asList("TABLE_NAME", "TABLE_TYPE",
695+
List<String> columnNames = Arrays.asList(
696+
"TABLE_NAME", "TABLE_TYPE",
696697
//nulls
697-
"REMARKS", "TABLE_CAT", "TABLE_SCHEM", "TABLE_TYPE", "TYPE_CAT", "TYPE_SCHEM",
698-
"TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"), rows));
698+
"REMARKS", "TABLE_CAT",
699+
"TABLE_SCHEM", "TABLE_TYPE",
700+
"TYPE_CAT", "TYPE_SCHEM",
701+
"TYPE_NAME", "SELF_REFERENCING_COL_NAME",
702+
"REF_GENERATION"
703+
);
704+
return sqlNullResultSet(columnNames, rows);
699705
} catch (Exception e) {
700706
throw new SQLException("Failed to retrieve table(s) description: " +
701707
"tableNamePattern=\"" + tableNamePattern + "\".", e);
@@ -707,18 +713,14 @@ public ResultSet getSchemas() throws SQLException {
707713
return rowOfNullsResultSet();
708714
}
709715

710-
private SQLNullResultSet rowOfNullsResultSet() {
711-
return new SQLNullResultSet(JDBCBridge.mock(Collections.<String>emptyList(), Collections.singletonList(Collections.emptyList())));
712-
}
713-
714716
@Override
715717
public ResultSet getCatalogs() throws SQLException {
716718
return rowOfNullsResultSet();
717719
}
718720

719721
@Override
720722
public ResultSet getTableTypes() throws SQLException {
721-
return new SQLResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.<Object>asList("TABLE"))));
723+
return asMetadataResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.<Object>asList("TABLE"))));
722724
}
723725

724726
@Override
@@ -748,12 +750,25 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
748750
}
749751
}
750752

751-
return new SQLNullResultSet((JDBCBridge.mock(
752-
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",
753-
//nulls
754-
"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"
755-
),
756-
rows)));
753+
List<String> columnNames = Arrays.asList(
754+
"TABLE_NAME", "COLUMN_NAME",
755+
"ORDINAL_POSITION", "DATA_TYPE",
756+
"TYPE_NAME", "NUM_PREC_RADIX",
757+
"NULLABLE", "IS_NULLABLE",
758+
"SOURCE_DATA_TYPE", "IS_AUTOINCREMENT",
759+
"IS_GENERATEDCOLUMN",
760+
//nulls
761+
"TABLE_CAT", "TABLE_SCHEM",
762+
"COLUMN_SIZE", "BUFFER_LENGTH",
763+
"DECIMAL_DIGITS", "REMARKS",
764+
"COLUMN_DEF", "SQL_DATA_TYPE",
765+
"SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH",
766+
"SCOPE_CATALOG", "SCOPE_SCHEMA",
767+
"SCOPE_TABLE"
768+
);
769+
return sqlNullResultSet(
770+
columnNames,
771+
rows);
757772
} catch (Exception e) {
758773
throw new SQLException("Error processing table column metadata: " +
759774
"tableNamePattern=\"" + tableNamePattern + "\"; " +
@@ -776,12 +791,12 @@ public ResultSet getTablePrivileges(String catalog, String schemaPattern, String
776791
@Override
777792
public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable)
778793
throws SQLException {
779-
return new SQLResultSet(JDBCBridge.EMPTY);
794+
return asMetadataResultSet(JDBCBridge.EMPTY);
780795
}
781796

782797
@Override
783798
public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
784-
return new SQLResultSet(JDBCBridge.EMPTY);
799+
return asMetadataResultSet(JDBCBridge.EMPTY);
785800
}
786801

787802
@Override
@@ -830,37 +845,37 @@ public int compare(List<Object> row0, List<Object> row1) {
830845
return col0.compareTo(col1);
831846
}
832847
});
833-
return new SQLNullResultSet((JDBCBridge.mock(colNames, rows)));
848+
return sqlNullResultSet(colNames, rows);
834849
} catch (Exception e) {
835850
throw new SQLException("Error processing metadata for table \"" + table + "\".", e);
836851
}
837852
}
838853

839854
@Override
840855
public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
841-
return new SQLResultSet(JDBCBridge.EMPTY);
856+
return asMetadataResultSet(JDBCBridge.EMPTY);
842857
}
843858

844859
@Override
845860
public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
846-
return new SQLResultSet(JDBCBridge.EMPTY);
861+
return asMetadataResultSet(JDBCBridge.EMPTY);
847862
}
848863

849864
@Override
850865
public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable)
851866
throws SQLException {
852-
return new SQLResultSet(JDBCBridge.EMPTY);
867+
return asMetadataResultSet(JDBCBridge.EMPTY);
853868
}
854869

855870
@Override
856871
public ResultSet getTypeInfo() throws SQLException {
857-
return new SQLResultSet(JDBCBridge.EMPTY);
872+
return asMetadataResultSet(JDBCBridge.EMPTY);
858873
}
859874

860875
@Override
861876
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
862877
throws SQLException {
863-
return new SQLResultSet(JDBCBridge.EMPTY);
878+
return asMetadataResultSet(JDBCBridge.EMPTY);
864879
}
865880

866881
@Override
@@ -926,7 +941,7 @@ public boolean supportsBatchUpdates() throws SQLException {
926941
@Override
927942
public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types)
928943
throws SQLException {
929-
return new SQLResultSet(JDBCBridge.EMPTY);
944+
return asMetadataResultSet(JDBCBridge.EMPTY);
930945
}
931946

932947
@Override
@@ -956,28 +971,34 @@ public boolean supportsGetGeneratedKeys() throws SQLException {
956971

957972
@Override
958973
public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
959-
return new SQLResultSet(JDBCBridge.EMPTY);
974+
return asMetadataResultSet(JDBCBridge.EMPTY);
960975
}
961976

962977
@Override
963978
public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
964-
return new SQLResultSet(JDBCBridge.EMPTY);
979+
return asMetadataResultSet(JDBCBridge.EMPTY);
965980
}
966981

967982
@Override
968983
public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern)
969984
throws SQLException {
970-
return new SQLResultSet(JDBCBridge.EMPTY);
985+
return asMetadataResultSet(JDBCBridge.EMPTY);
971986
}
972987

988+
/**
989+
* {@inheritDoc}
990+
*
991+
* Support of {@link ResultSet#CLOSE_CURSORS_AT_COMMIT} is not
992+
* available now because it requires cursor transaction support.
993+
*/
973994
@Override
974995
public boolean supportsResultSetHoldability(int holdability) throws SQLException {
975-
return false;
996+
return holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT;
976997
}
977998

978999
@Override
9791000
public int getResultSetHoldability() throws SQLException {
980-
return 0;
1001+
return ResultSet.HOLD_CURSORS_OVER_COMMIT;
9811002
}
9821003

9831004
@Override
@@ -1037,25 +1058,25 @@ public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
10371058

10381059
@Override
10391060
public ResultSet getClientInfoProperties() throws SQLException {
1040-
return new SQLResultSet(JDBCBridge.EMPTY);
1061+
return asMetadataResultSet(JDBCBridge.EMPTY);
10411062
}
10421063

10431064
@Override
10441065
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
10451066
throws SQLException {
1046-
return new SQLResultSet(JDBCBridge.EMPTY);
1067+
return asMetadataResultSet(JDBCBridge.EMPTY);
10471068
}
10481069

10491070
@Override
10501071
public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern)
10511072
throws SQLException {
1052-
return new SQLResultSet(JDBCBridge.EMPTY);
1073+
return asMetadataResultSet(JDBCBridge.EMPTY);
10531074
}
10541075

10551076
@Override
10561077
public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
10571078
throws SQLException {
1058-
return new SQLResultSet(JDBCBridge.EMPTY);
1079+
return asMetadataResultSet(JDBCBridge.EMPTY);
10591080
}
10601081

10611082
@Override
@@ -1073,6 +1094,14 @@ public boolean isWrapperFor(Class<?> iface) throws SQLException {
10731094
throw new SQLFeatureNotSupportedException();
10741095
}
10751096

1097+
private ResultSet asMetadataResultSet(JDBCBridge jdbcBridge) throws SQLException {
1098+
return createMetadataStatement().executeMetadata(jdbcBridge);
1099+
}
1100+
1101+
private SQLStatement createMetadataStatement() throws SQLException {
1102+
return connection.createStatement().unwrap(SQLStatement.class);
1103+
}
1104+
10761105
private static <T> T ensureType(Class<T> cls, Object v) throws Exception {
10771106
if (v == null || !cls.isAssignableFrom(v.getClass())) {
10781107
throw new Exception(String.format("Wrong value type '%s', expected '%s'.",
@@ -1085,8 +1114,16 @@ private static <T> T checkType(Class<T> cls, Object v) {
10851114
return (v != null && cls.isAssignableFrom(v.getClass())) ? cls.cast(v) : null;
10861115
}
10871116

1088-
private ResultSet emptyResultSet(List<String> colNames) {
1089-
return new SQLNullResultSet((JDBCBridge.mock(colNames, Collections.<List<Object>>emptyList())));
1117+
private SQLNullResultSet rowOfNullsResultSet() throws SQLException {
1118+
return sqlNullResultSet(Collections.emptyList(), Collections.emptyList());
1119+
}
1120+
1121+
private SQLNullResultSet emptyResultSet(List<String> colNames) throws SQLException {
1122+
return sqlNullResultSet(colNames, Collections.emptyList());
1123+
}
1124+
1125+
private SQLNullResultSet sqlNullResultSet(List<String> colNames, List<List<Object>> rows) throws SQLException {
1126+
return new SQLNullResultSet(JDBCBridge.mock(colNames, rows), createMetadataStatement());
10901127
}
10911128

10921129
}

‎src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,33 @@
2525
import java.util.Map;
2626

2727
public class SQLPreparedStatement extends SQLStatement implements PreparedStatement {
28+
2829
final static String INVALID_CALL_MSG = "The method cannot be called on a PreparedStatement.";
2930
final String sql;
3031
final Map<Integer, Object> params;
3132

3233

33-
public SQLPreparedStatement(SQLConnection connection, String sql) {
34+
public SQLPreparedStatement(SQLConnection connection, String sql) throws SQLException {
3435
super(connection);
3536
this.sql = sql;
36-
this.params = new HashMap<Integer, Object>();
37+
this.params = new HashMap<>();
38+
}
39+
40+
public SQLPreparedStatement(SQLConnection connection,
41+
String sql,
42+
int resultSetType,
43+
int resultSetConcurrency,
44+
int resultSetHoldability) throws SQLException {
45+
super(connection, resultSetType, resultSetConcurrency, resultSetHoldability);
46+
this.sql = sql;
47+
this.params = new HashMap<>();
3748
}
3849

3950
@Override
4051
public ResultSet executeQuery() throws SQLException {
52+
checkNotClosed();
4153
discardLastResults();
42-
return connection.executeQuery(sql, getParams());
54+
return createResultSet(connection.executeQuery(sql, getParams()));
4355
}
4456

4557
protected Object[] getParams() throws SQLException {
@@ -56,93 +68,94 @@ protected Object[] getParams() throws SQLException {
5668

5769
@Override
5870
public int executeUpdate() throws SQLException {
71+
checkNotClosed();
5972
discardLastResults();
6073
return connection.executeUpdate(sql, getParams());
6174
}
6275

6376
@Override
6477
public void setNull(int parameterIndex, int sqlType) throws SQLException {
65-
params.put(parameterIndex, null);
78+
setParameter(parameterIndex, null);
6679
}
6780

6881
@Override
69-
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
70-
params.put(parameterIndex, x);
82+
public void setBoolean(int parameterIndex, boolean parameterValue) throws SQLException {
83+
setParameter(parameterIndex, parameterValue);
7184
}
7285

7386
@Override
74-
public void setByte(int parameterIndex, byte x) throws SQLException {
75-
params.put(parameterIndex, x);
87+
public void setByte(int parameterIndex, byte parameterValue) throws SQLException {
88+
setParameter(parameterIndex, parameterValue);
7689
}
7790

7891
@Override
79-
public void setShort(int parameterIndex, short x) throws SQLException {
80-
params.put(parameterIndex, x);
92+
public void setShort(int parameterIndex, short parameterValue) throws SQLException {
93+
setParameter(parameterIndex, parameterValue);
8194
}
8295

8396
@Override
84-
public void setInt(int parameterIndex, int x) throws SQLException {
85-
params.put(parameterIndex, x);
97+
public void setInt(int parameterIndex, int parameterValue) throws SQLException {
98+
setParameter(parameterIndex, parameterValue);
8699
}
87100

88101
@Override
89-
public void setLong(int parameterIndex, long x) throws SQLException {
90-
params.put(parameterIndex, x);
102+
public void setLong(int parameterIndex, long parameterValue) throws SQLException {
103+
setParameter(parameterIndex, parameterValue);
91104
}
92105

93106
@Override
94-
public void setFloat(int parameterIndex, float x) throws SQLException {
95-
params.put(parameterIndex, x);
107+
public void setFloat(int parameterIndex, float parameterValue) throws SQLException {
108+
setParameter(parameterIndex, parameterValue);
96109
}
97110

98111
@Override
99-
public void setDouble(int parameterIndex, double x) throws SQLException {
100-
params.put(parameterIndex, x);
112+
public void setDouble(int parameterIndex, double parameterValue) throws SQLException {
113+
setParameter(parameterIndex, parameterValue);
101114
}
102115

103116
@Override
104-
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
105-
params.put(parameterIndex, x);
117+
public void setBigDecimal(int parameterIndex, BigDecimal parameterValue) throws SQLException {
118+
setParameter(parameterIndex, parameterValue);
106119
}
107120

108121
@Override
109-
public void setString(int parameterIndex, String x) throws SQLException {
110-
params.put(parameterIndex, x);
122+
public void setString(int parameterIndex, String parameterValue) throws SQLException {
123+
setParameter(parameterIndex, parameterValue);
111124
}
112125

113126
@Override
114-
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
115-
params.put(parameterIndex, x);
127+
public void setBytes(int parameterIndex, byte[] parameterValue) throws SQLException {
128+
setParameter(parameterIndex, parameterValue);
116129
}
117130

118131
@Override
119-
public void setDate(int parameterIndex, Date x) throws SQLException {
120-
params.put(parameterIndex, x);
132+
public void setDate(int parameterIndex, Date parameterValue) throws SQLException {
133+
setParameter(parameterIndex, parameterValue);
121134
}
122135

123136
@Override
124-
public void setTime(int parameterIndex, Time x) throws SQLException {
125-
params.put(parameterIndex, x);
137+
public void setTime(int parameterIndex, Time parameterValue) throws SQLException {
138+
setParameter(parameterIndex, parameterValue);
126139
}
127140

128141
@Override
129-
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
130-
params.put(parameterIndex, x);
142+
public void setTimestamp(int parameterIndex, Timestamp parameterValue) throws SQLException {
143+
setParameter(parameterIndex, parameterValue);
131144
}
132145

133146
@Override
134-
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
135-
params.put(parameterIndex, x);
147+
public void setAsciiStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
148+
setParameter(parameterIndex, parameterValue);
136149
}
137150

138151
@Override
139-
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
140-
params.put(parameterIndex, x);
152+
public void setUnicodeStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
153+
setParameter(parameterIndex, parameterValue);
141154
}
142155

143156
@Override
144-
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
145-
params.put(parameterIndex, x);
157+
public void setBinaryStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
158+
setParameter(parameterIndex, parameterValue);
146159
}
147160

148161
@Override
@@ -152,16 +165,22 @@ public void clearParameters() throws SQLException {
152165

153166
@Override
154167
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
155-
params.put(parameterIndex, x);
168+
setObject(parameterIndex, x, targetSqlType, -1);
156169
}
157170

158171
@Override
159-
public void setObject(int parameterIndex, Object x) throws SQLException {
160-
params.put(parameterIndex, x);
172+
public void setObject(int parameterIndex, Object value) throws SQLException {
173+
setParameter(parameterIndex, value);
174+
}
175+
176+
private void setParameter(int parameterIndex, Object value) throws SQLException {
177+
checkNotClosed();
178+
params.put(parameterIndex, value);
161179
}
162180

163181
@Override
164182
public boolean execute() throws SQLException {
183+
checkNotClosed();
165184
discardLastResults();
166185
return handleResult(connection.execute(sql, getParams()));
167186
}
@@ -197,28 +216,28 @@ public ResultSetMetaData getMetaData() throws SQLException {
197216
}
198217

199218
@Override
200-
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
201-
params.put(parameterIndex, x);
219+
public void setDate(int parameterIndex, Date parameterValue, Calendar calendar) throws SQLException {
220+
setParameter(parameterIndex, parameterValue);
202221
}
203222

204223
@Override
205-
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
206-
params.put(parameterIndex, x);
224+
public void setTime(int parameterIndex, Time parameterValue, Calendar calendar) throws SQLException {
225+
setParameter(parameterIndex, parameterValue);
207226
}
208227

209228
@Override
210-
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
211-
params.put(parameterIndex, x);
229+
public void setTimestamp(int parameterIndex, Timestamp parameterValue, Calendar calendar) throws SQLException {
230+
setParameter(parameterIndex, parameterValue);
212231
}
213232

214233
@Override
215234
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
216-
params.put(parameterIndex, null);
235+
setParameter(parameterIndex, null);
217236
}
218237

219238
@Override
220-
public void setURL(int parameterIndex, URL x) throws SQLException {
221-
params.put(parameterIndex, x.toString());
239+
public void setURL(int parameterIndex, URL parameterValue) throws SQLException {
240+
setParameter(parameterIndex, parameterValue.toString());
222241
}
223242

224243
@Override
@@ -232,8 +251,8 @@ public void setRowId(int parameterIndex, RowId x) throws SQLException {
232251
}
233252

234253
@Override
235-
public void setNString(int parameterIndex, String value) throws SQLException {
236-
params.put(parameterIndex, value);
254+
public void setNString(int parameterIndex, String parameterValue) throws SQLException {
255+
setParameter(parameterIndex, parameterValue);
237256
}
238257

239258
@Override
@@ -267,8 +286,8 @@ public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException
267286
}
268287

269288
@Override
270-
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
271-
params.put(parameterIndex, x);
289+
public void setObject(int parameterIndex, Object parameterValue, int targetSqlType, int scaleOrLength) throws SQLException {
290+
setParameter(parameterIndex, parameterValue);
272291
}
273292

274293
@Override

‎src/main/java/org/tarantool/jdbc/SQLResultSet.java

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool.jdbc;
22

3+
import org.tarantool.JDBCBridge;
4+
35
import java.io.ByteArrayInputStream;
46
import java.io.InputStream;
57
import java.io.Reader;
@@ -19,6 +21,7 @@
1921
import java.sql.RowId;
2022
import java.sql.SQLException;
2123
import java.sql.SQLFeatureNotSupportedException;
24+
import java.sql.SQLNonTransientException;
2225
import java.sql.SQLWarning;
2326
import java.sql.SQLXML;
2427
import java.sql.Statement;
@@ -29,26 +32,46 @@
2932
import java.util.ListIterator;
3033
import java.util.Map;
3134

32-
import org.tarantool.JDBCBridge;
33-
3435
@SuppressWarnings("Since15")
3536
public class SQLResultSet implements ResultSet {
36-
ListIterator<List<Object>> iterator;
37-
final JDBCBridge bridge;
38-
final SQLResultSetMetaData metaData;
3937

40-
int maxRows;
41-
List<Object> row = null;
38+
private ListIterator<List<Object>> iterator;
39+
private JDBCBridge bridge;
40+
private final SQLResultSetMetaData metaData;
41+
42+
private final Statement statement;
43+
private int maxRows;
44+
private List<Object> row = null;
4245

46+
private final int type;
47+
private final int concurrencyLevel;
48+
private final int holdability;
4349

44-
public SQLResultSet(JDBCBridge bridge) {
50+
public SQLResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws SQLException {
4551
this.bridge = bridge;
4652
iterator = bridge.iterator();
4753
metaData = new SQLResultSetMetaData(bridge);
54+
statement = ownerStatement;
55+
type = statement.getResultSetType();
56+
concurrencyLevel = statement.getResultSetConcurrency();
57+
holdability = statement.getResultSetHoldability();
58+
}
59+
60+
public int getMaxRows() {
61+
return maxRows;
62+
}
63+
64+
public void setMaxRows(int maxRows) {
65+
this.maxRows = maxRows;
66+
}
67+
68+
List<Object> getCurrentRow() {
69+
return row;
4870
}
4971

5072
@Override
5173
public boolean next() throws SQLException {
74+
checkNotClosed();
5275
if (iterator.hasNext() && (maxRows == 0 || iterator.nextIndex() < maxRows)) {
5376
row = iterator.next();
5477
return true;
@@ -300,32 +323,38 @@ public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
300323

301324
@Override
302325
public boolean isBeforeFirst() throws SQLException {
326+
checkNotClosed();
303327
return row == null && iterator.previousIndex() == -1;
304328
}
305329

306330
@Override
307331
public boolean isAfterLast() throws SQLException {
332+
checkNotClosed();
308333
return iterator.nextIndex() == bridge.size() && row == null;
309334
}
310335

311336
@Override
312337
public boolean isFirst() throws SQLException {
338+
checkNotClosed();
313339
return iterator.previousIndex() == 0;
314340
}
315341

316342
@Override
317343
public boolean isLast() throws SQLException {
344+
checkNotClosed();
318345
return iterator.nextIndex() == bridge.size();
319346
}
320347

321348
@Override
322349
public void beforeFirst() throws SQLException {
350+
checkNotClosed();
323351
row = null;
324352
iterator = bridge.iterator();
325353
}
326354

327355
@Override
328356
public void afterLast() throws SQLException {
357+
checkNotClosed();
329358
while (next()) {
330359
}
331360
}
@@ -338,6 +367,7 @@ public boolean first() throws SQLException {
338367

339368
@Override
340369
public boolean last() throws SQLException {
370+
checkNotClosed();
341371
while (iterator.hasNext()) {
342372
next();
343373
}
@@ -346,6 +376,7 @@ public boolean last() throws SQLException {
346376

347377
@Override
348378
public int getRow() throws SQLException {
379+
checkNotClosed();
349380
return iterator.previousIndex() + 1;
350381
}
351382

@@ -361,6 +392,7 @@ public boolean absolute(int row) throws SQLException {
361392

362393
@Override
363394
public boolean relative(int rows) throws SQLException {
395+
checkNotClosed();
364396
for (int i = 0; i < rows && iterator.hasNext(); i++) {
365397
next();
366398
}
@@ -369,6 +401,7 @@ public boolean relative(int rows) throws SQLException {
369401

370402
@Override
371403
public boolean previous() throws SQLException {
404+
checkNotClosed();
372405
if (iterator.hasPrevious()) {
373406
iterator.previous();
374407
return true;
@@ -378,13 +411,15 @@ public boolean previous() throws SQLException {
378411

379412
@Override
380413
public void setFetchDirection(int direction) throws SQLException {
414+
checkNotClosed();
381415
if (direction != ResultSet.FETCH_FORWARD) {
382416
throw new SQLException("TYPE_FORWARD_ONLY");
383417
}
384418
}
385419

386420
@Override
387421
public int getFetchDirection() throws SQLException {
422+
checkNotClosed();
388423
return ResultSet.FETCH_FORWARD;
389424
}
390425

@@ -400,12 +435,14 @@ public int getFetchSize() throws SQLException {
400435

401436
@Override
402437
public int getType() throws SQLException {
403-
return ResultSet.TYPE_FORWARD_ONLY;
438+
checkNotClosed();
439+
return type;
404440
}
405441

406442
@Override
407443
public int getConcurrency() throws SQLException {
408-
throw new SQLFeatureNotSupportedException();
444+
checkNotClosed();
445+
return concurrencyLevel;
409446
}
410447

411448
@Override
@@ -813,12 +850,13 @@ public void updateRowId(String columnLabel, RowId x) throws SQLException {
813850

814851
@Override
815852
public int getHoldability() throws SQLException {
816-
throw new SQLFeatureNotSupportedException();
853+
checkNotClosed();
854+
return holdability;
817855
}
818856

819857
@Override
820858
public boolean isClosed() throws SQLException {
821-
return false;
859+
return statement.isClosed();
822860
}
823861

824862
@Override
@@ -1059,4 +1097,11 @@ public String toString() {
10591097
", row=" + row +
10601098
'}';
10611099
}
1100+
1101+
protected void checkNotClosed() throws SQLException {
1102+
if (isClosed()) {
1103+
throw new SQLNonTransientException("ResultSet is closed.");
1104+
}
1105+
}
1106+
10621107
}

‎src/main/java/org/tarantool/jdbc/SQLStatement.java

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,55 @@
11
package org.tarantool.jdbc;
22

3+
import org.tarantool.JDBCBridge;
4+
35
import java.sql.Connection;
46
import java.sql.ResultSet;
57
import java.sql.SQLException;
68
import java.sql.SQLFeatureNotSupportedException;
9+
import java.sql.SQLNonTransientException;
710
import java.sql.SQLWarning;
811
import java.sql.Statement;
912

1013
@SuppressWarnings("Since15")
1114
public class SQLStatement implements Statement {
15+
1216
protected final SQLConnection connection;
17+
1318
private SQLResultSet resultSet;
19+
private final int resultSetType;
20+
private final int resultSetConcurrency;
21+
private final int resultSetHoldability;
22+
1423
private int updateCount;
1524
private int maxRows;
1625

17-
protected SQLStatement(SQLConnection sqlConnection) {
26+
protected SQLStatement(SQLConnection sqlConnection) throws SQLException {
27+
this.connection = sqlConnection;
28+
this.resultSetType = ResultSet.TYPE_FORWARD_ONLY;
29+
this.resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
30+
this.resultSetHoldability = sqlConnection.getHoldability();
31+
}
32+
33+
protected SQLStatement(SQLConnection sqlConnection,
34+
int resultSetType,
35+
int resultSetConcurrency,
36+
int resultSetHoldability) throws SQLException {
1837
this.connection = sqlConnection;
38+
this.resultSetType = resultSetType;
39+
this.resultSetConcurrency = resultSetConcurrency;
40+
this.resultSetHoldability = resultSetHoldability;
1941
}
2042

2143
@Override
2244
public ResultSet executeQuery(String sql) throws SQLException {
45+
checkNotClosed();
2346
discardLastResults();
24-
return connection.executeQuery(sql);
47+
return createResultSet(connection.executeQuery(sql));
2548
}
2649

2750
@Override
2851
public int executeUpdate(String sql) throws SQLException {
52+
checkNotClosed();
2953
discardLastResults();
3054
return connection.executeUpdate(sql);
3155
}
@@ -47,15 +71,20 @@ public void setMaxFieldSize(int max) throws SQLException {
4771

4872
@Override
4973
public int getMaxRows() throws SQLException {
74+
checkNotClosed();
5075
return maxRows;
5176
}
5277

5378
@Override
54-
public void setMaxRows(int max) throws SQLException {
55-
maxRows = max;
56-
if(resultSet!=null) {
57-
resultSet.maxRows = maxRows;
58-
}
79+
public void setMaxRows(int maxRows) throws SQLException {
80+
checkNotClosed();
81+
if (maxRows < 0) {
82+
throw new SQLNonTransientException("Max rows parameter can't be a negative value");
83+
}
84+
this.maxRows = maxRows;
85+
if (resultSet != null) {
86+
resultSet.setMaxRows(this.maxRows);
87+
}
5988
}
6089

6190
@Override
@@ -95,12 +124,14 @@ public void setCursorName(String name) throws SQLException {
95124

96125
@Override
97126
public boolean execute(String sql) throws SQLException {
127+
checkNotClosed();
98128
discardLastResults();
99129
return handleResult(connection.execute(sql));
100130
}
101131

102132
@Override
103133
public ResultSet getResultSet() throws SQLException {
134+
checkNotClosed();
104135
try {
105136
return resultSet;
106137
} finally {
@@ -110,6 +141,7 @@ public ResultSet getResultSet() throws SQLException {
110141

111142
@Override
112143
public int getUpdateCount() throws SQLException {
144+
checkNotClosed();
113145
try {
114146
return updateCount;
115147
} finally {
@@ -119,23 +151,28 @@ public int getUpdateCount() throws SQLException {
119151

120152
@Override
121153
public boolean getMoreResults() throws SQLException {
154+
checkNotClosed();
122155
return false;
123156
}
124157

125158
@Override
126159
public void setFetchDirection(int direction) throws SQLException {
160+
checkNotClosed();
127161
if (direction != ResultSet.FETCH_FORWARD) {
128162
throw new SQLFeatureNotSupportedException();
129163
}
130164
}
131165

132166
@Override
133167
public int getFetchDirection() throws SQLException {
168+
checkNotClosed();
134169
return ResultSet.FETCH_FORWARD;
135170
}
136171

137172
@Override
138173
public void setFetchSize(int rows) throws SQLException {
174+
checkNotClosed();
175+
// no-op
139176
}
140177

141178
@Override
@@ -145,12 +182,14 @@ public int getFetchSize() throws SQLException {
145182

146183
@Override
147184
public int getResultSetConcurrency() throws SQLException {
148-
throw new SQLFeatureNotSupportedException();
185+
checkNotClosed();
186+
return resultSetConcurrency;
149187
}
150188

151189
@Override
152190
public int getResultSetType() throws SQLException {
153-
return ResultSet.TYPE_FORWARD_ONLY;
191+
checkNotClosed();
192+
return resultSetType;
154193
}
155194

156195
@Override
@@ -175,6 +214,7 @@ public Connection getConnection() throws SQLException {
175214

176215
@Override
177216
public boolean getMoreResults(int current) throws SQLException {
217+
checkNotClosed();
178218
return false;
179219
}
180220

@@ -215,7 +255,8 @@ public boolean execute(String sql, String[] columnNames) throws SQLException {
215255

216256
@Override
217257
public int getResultSetHoldability() throws SQLException {
218-
throw new SQLFeatureNotSupportedException();
258+
checkNotClosed();
259+
return resultSetHoldability;
219260
}
220261

221262
@Override
@@ -240,17 +281,21 @@ public void closeOnCompletion() throws SQLException {
240281

241282
@Override
242283
public boolean isCloseOnCompletion() throws SQLException {
284+
checkNotClosed();
243285
return false;
244286
}
245287

246288
@Override
247-
public <T> T unwrap(Class<T> iface) throws SQLException {
248-
throw new SQLFeatureNotSupportedException();
289+
public <T> T unwrap(Class<T> type) throws SQLException {
290+
if (isWrapperFor(type)) {
291+
return type.cast(this);
292+
}
293+
throw new SQLNonTransientException("Statement does not wrap " + type.getName());
249294
}
250295

251296
@Override
252-
public boolean isWrapperFor(Class<?> iface) throws SQLException {
253-
throw new SQLFeatureNotSupportedException();
297+
public boolean isWrapperFor(Class<?> type) throws SQLException {
298+
return type.isAssignableFrom(this.getClass());
254299
}
255300

256301
/**
@@ -274,10 +319,10 @@ protected void discardLastResults() {
274319
* @param result The result of SQL statement execution.
275320
* @return {@code true}, if the result is a ResultSet object.
276321
*/
277-
protected boolean handleResult(Object result) {
278-
if (result instanceof SQLResultSet) {
279-
resultSet = (SQLResultSet) result;
280-
resultSet.maxRows = maxRows;
322+
protected boolean handleResult(Object result) throws SQLException {
323+
if (result instanceof JDBCBridge) {
324+
resultSet = createResultSet((JDBCBridge) result);
325+
resultSet.setMaxRows(maxRows);
281326
updateCount = -1;
282327
return true;
283328
} else {
@@ -286,4 +331,27 @@ protected boolean handleResult(Object result) {
286331
return false;
287332
}
288333
}
334+
335+
/**
336+
* Returns {@link ResultSet} which will be initialized by <code>data</code>
337+
*
338+
* @param data predefined result to be wrapped by {@link ResultSet}
339+
* @return wrapped result
340+
* @throws SQLException if a database access error occurs or
341+
* this method is called on a closed <code>Statement</code>
342+
*/
343+
public ResultSet executeMetadata(JDBCBridge data) throws SQLException {
344+
checkNotClosed();
345+
return createResultSet(data);
346+
}
347+
348+
protected SQLResultSet createResultSet(JDBCBridge result) throws SQLException {
349+
return new SQLResultSet(result, this);
350+
}
351+
352+
protected void checkNotClosed() throws SQLException {
353+
if (isClosed()) {
354+
throw new SQLNonTransientException("Statement is closed.");
355+
}
356+
}
289357
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.tarantool.util;
2+
3+
public enum SQLStates {
4+
5+
INVALID_PARAMETER_VALUE("22023"),
6+
CONNECTION_DOES_NOT_EXIST("08003");
7+
8+
private final String sqlState;
9+
10+
SQLStates(String sqlState) {
11+
this.sqlState = sqlState;
12+
}
13+
14+
public String getSqlState() {
15+
return sqlState;
16+
}
17+
}

‎src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import java.net.Socket;
99
import java.sql.DatabaseMetaData;
1010
import java.sql.PreparedStatement;
11+
import java.sql.ResultSet;
1112
import java.sql.SQLException;
13+
import java.sql.SQLFeatureNotSupportedException;
1214
import java.sql.Statement;
1315

1416
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -20,6 +22,7 @@
2022

2123
@SuppressWarnings("Since15")
2224
public class JdbcConnectionIT extends AbstractJdbcIT {
25+
2326
@Test
2427
public void testCreateStatement() throws SQLException {
2528
Statement stmt = conn.createStatement();
@@ -71,7 +74,7 @@ public void execute() throws Throwable {
7174
Field sock = TarantoolConnection.class.getDeclaredField("socket");
7275
sock.setAccessible(true);
7376

74-
assertEquals(3000, ((Socket)sock.get(tntCon.get(conn))).getSoTimeout());
77+
assertEquals(3000, ((Socket) sock.get(tntCon.get(conn))).getSoTimeout());
7578
}
7679

7780
@Test
@@ -85,15 +88,20 @@ public void testClosedConnection() throws SQLException {
8588
@Override
8689
public void execute() throws Throwable {
8790
switch (step) {
88-
case 0: conn.createStatement();
91+
case 0:
92+
conn.createStatement();
8993
break;
90-
case 1: conn.prepareStatement("TEST");
94+
case 1:
95+
conn.prepareStatement("TEST");
9196
break;
92-
case 2: conn.getMetaData();
97+
case 2:
98+
conn.getMetaData();
9399
break;
94-
case 3: conn.getNetworkTimeout();
100+
case 3:
101+
conn.getNetworkTimeout();
95102
break;
96-
case 4: conn.setNetworkTimeout(null, 1000);
103+
case 4:
104+
conn.setNetworkTimeout(null, 1000);
97105
break;
98106
default:
99107
fail();
@@ -104,4 +112,84 @@ public void execute() throws Throwable {
104112
}
105113
assertEquals(5, i);
106114
}
107-
}
115+
116+
@Test
117+
public void testConnectionUnwrap() throws SQLException {
118+
assertEquals(conn, conn.unwrap(SQLConnection.class));
119+
assertThrows(SQLException.class, () -> conn.unwrap(Integer.class));
120+
}
121+
122+
@Test
123+
public void testConnectionIsWrapperFor() throws SQLException {
124+
assertTrue(conn.isWrapperFor(SQLConnection.class));
125+
assertFalse(conn.isWrapperFor(Integer.class));
126+
}
127+
128+
@Test
129+
public void testDefaultGetHoldability() throws SQLException {
130+
// default connection holdability should be equal to metadata one
131+
assertEquals(conn.getMetaData().getResultSetHoldability(), conn.getHoldability());
132+
}
133+
134+
@Test
135+
public void testSetAndGetHoldability() throws SQLException {
136+
conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
137+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, conn.getHoldability());
138+
139+
assertThrows(SQLFeatureNotSupportedException.class, () -> conn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT));
140+
assertThrows(SQLException.class, () -> conn.setHoldability(Integer.MAX_VALUE));
141+
142+
assertThrows(SQLException.class, () -> {
143+
conn.close();
144+
conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
145+
});
146+
}
147+
148+
@Test
149+
public void testCreateHoldableStatement() throws SQLException {
150+
Statement statement = conn.createStatement();
151+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
152+
153+
statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
154+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
155+
156+
statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
157+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
158+
159+
assertThrows(SQLException.class, () -> {
160+
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, Integer.MAX_VALUE);
161+
});
162+
assertThrows(SQLFeatureNotSupportedException.class, () -> {
163+
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
164+
});
165+
assertThrows(SQLException.class, () -> {
166+
conn.close();
167+
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
168+
});
169+
}
170+
171+
@Test
172+
public void testPrepareHoldableStatement() throws SQLException {
173+
String sqlString = "TEST";
174+
Statement statement = conn.prepareStatement(sqlString);
175+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
176+
177+
statement = conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
178+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
179+
180+
statement = conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
181+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
182+
183+
assertThrows(SQLException.class, () -> {
184+
conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, Integer.MAX_VALUE);
185+
});
186+
assertThrows(SQLFeatureNotSupportedException.class, () -> {
187+
conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
188+
});
189+
assertThrows(SQLException.class, () -> {
190+
conn.close();
191+
conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
192+
});
193+
}
194+
195+
}

‎src/test/java/org/tarantool/jdbc/JdbcDatabaseMetaDataIT.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package org.tarantool.jdbc;
22

3-
import org.junit.jupiter.api.Test;
43
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
55
import org.junit.jupiter.api.function.Executable;
66

77
import java.sql.DatabaseMetaData;
88
import java.sql.ResultSet;
99
import java.sql.ResultSetMetaData;
1010
import java.sql.SQLException;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
1113

1214
import static org.junit.jupiter.api.Assertions.assertEquals;
1315
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,9 +19,6 @@
1719
import static org.junit.jupiter.api.Assertions.assertTrue;
1820
import static org.junit.jupiter.api.Assertions.fail;
1921

20-
import java.util.regex.Matcher;
21-
import java.util.regex.Pattern;
22-
2322
public class JdbcDatabaseMetaDataIT extends AbstractJdbcIT {
2423
private DatabaseMetaData meta;
2524

@@ -239,4 +238,20 @@ public void testGetDriverNameVersion() throws SQLException {
239238
assertEquals(majorVersion, majorVersionMatched);
240239
assertEquals(minorVersion, minorVersionMatched);
241240
}
241+
242+
@Test
243+
public void testGetResultSetHoldability() throws SQLException {
244+
int resultSetHoldability = meta.getResultSetHoldability();
245+
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, resultSetHoldability);
246+
}
247+
248+
@Test
249+
public void testSupportsResultSetHoldability() throws SQLException {
250+
assertTrue(meta.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));
251+
assertFalse(meta.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT));
252+
assertFalse(meta.supportsResultSetHoldability(Integer.MAX_VALUE));
253+
assertFalse(meta.supportsResultSetHoldability(Integer.MIN_VALUE));
254+
assertFalse(meta.supportsResultSetHoldability(42));
255+
}
256+
242257
}

‎src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package org.tarantool.jdbc;
22

3+
import org.junit.jupiter.api.AfterEach;
34
import org.junit.jupiter.api.Disabled;
45
import org.junit.jupiter.api.Test;
5-
import org.junit.jupiter.api.AfterEach;
66
import org.junit.jupiter.api.function.Executable;
77

88
import java.math.BigDecimal;
@@ -157,7 +157,7 @@ public void execute() throws Throwable {
157157
}
158158
}
159159
});
160-
assertEquals("Connection is closed.", e.getMessage());
160+
assertEquals("Statement is closed.", e.getMessage());
161161
}
162162
assertEquals(3, i);
163163
}

‎src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package org.tarantool.jdbc;
22

33
import org.junit.jupiter.api.AfterEach;
4-
import org.junit.jupiter.api.Test;
54
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
66

77
import java.math.BigDecimal;
8+
import java.sql.DatabaseMetaData;
89
import java.sql.ResultSet;
910
import java.sql.SQLException;
1011
import java.sql.Statement;
@@ -16,10 +17,12 @@
1617

1718
public class JdbcResultSetIT extends JdbcTypesIT {
1819
private Statement stmt;
20+
private DatabaseMetaData metaData;
1921

2022
@BeforeEach
2123
public void setUp() throws Exception {
2224
stmt = conn.createStatement();
25+
metaData = conn.getMetaData();
2326
}
2427

2528
@AfterEach
@@ -121,4 +124,12 @@ public void testGetByteArrayColumn() throws SQLException {
121124
.setValues(BINARY_VALS)
122125
.testGetColumn();
123126
}
127+
128+
@Test
129+
public void testHoldability() throws SQLException {
130+
ResultSet resultSet = stmt.executeQuery("SELECT * FROM test WHERE id < 0");
131+
assertNotNull(resultSet);
132+
assertEquals(metaData.getResultSetHoldability(), resultSet.getHoldability());
133+
}
134+
124135
}

‎src/test/java/org/tarantool/jdbc/JdbcStatementIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import java.sql.SQLException;
1010
import java.sql.Statement;
1111

12+
import static org.junit.jupiter.api.Assertions.assertEquals;
1213
import static org.junit.jupiter.api.Assertions.assertFalse;
1314
import static org.junit.jupiter.api.Assertions.assertNotNull;
1415
import static org.junit.jupiter.api.Assertions.assertThrows;
1516
import static org.junit.jupiter.api.Assertions.assertTrue;
16-
import static org.junit.jupiter.api.Assertions.assertEquals;
1717
import static org.junit.jupiter.api.Assertions.fail;
1818

1919
public class JdbcStatementIT extends AbstractJdbcIT {
@@ -89,7 +89,7 @@ public void execute() throws Throwable {
8989
}
9090
}
9191
});
92-
assertEquals("Connection is closed.", e.getMessage());
92+
assertEquals("Statement is closed.", e.getMessage());
9393
}
9494
assertEquals(3, i);
9595
}

0 commit comments

Comments
 (0)
Please sign in to comment.