From 268eafaeafa32ef0f3a3b1f076af9e7303960bfe Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 12 Mar 2019 22:22:31 +0300 Subject: [PATCH] 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 --- src/main/java/org/tarantool/JDBCBridge.java | 5 +- .../org/tarantool/jdbc/SQLConnection.java | 138 ++++++++++++------ .../tarantool/jdbc/SQLDatabaseMetadata.java | 129 ++++++++++------ .../tarantool/jdbc/SQLPreparedStatement.java | 123 +++++++++------- .../java/org/tarantool/jdbc/SQLResultSet.java | 69 +++++++-- .../java/org/tarantool/jdbc/SQLStatement.java | 104 ++++++++++--- .../java/org/tarantool/util/SQLStates.java | 17 +++ .../org/tarantool/jdbc/JdbcConnectionIT.java | 102 ++++++++++++- .../jdbc/JdbcDatabaseMetaDataIT.java | 23 ++- .../jdbc/JdbcPreparedStatementIT.java | 4 +- .../org/tarantool/jdbc/JdbcResultSetIT.java | 13 +- .../org/tarantool/jdbc/JdbcStatementIT.java | 4 +- 12 files changed, 544 insertions(+), 187 deletions(-) create mode 100644 src/main/java/org/tarantool/util/SQLStates.java diff --git a/src/main/java/org/tarantool/JDBCBridge.java b/src/main/java/org/tarantool/JDBCBridge.java index 9c5eafe6..5693493f 100644 --- a/src/main/java/org/tarantool/JDBCBridge.java +++ b/src/main/java/org/tarantool/JDBCBridge.java @@ -11,6 +11,7 @@ import org.tarantool.protocol.TarantoolPacket; public class JDBCBridge { + public static final JDBCBridge EMPTY = new JDBCBridge(Collections.emptyList(), Collections.>emptyList()); final List sqlMetadata; @@ -41,7 +42,7 @@ public static int update(TarantoolConnection connection, String sql, Object ... public static JDBCBridge mock(List fields, List> values) { List meta = new ArrayList(fields.size()); - for(String field:fields) { + for(String field : fields) { meta.add(new TarantoolBase.SQLMetaData(field)); } return new JDBCBridge(meta, values); @@ -51,7 +52,7 @@ public static Object execute(TarantoolConnection connection, String sql, Object TarantoolPacket pack = connection.sql(sql, params); Long rowCount = SqlProtoUtils.getSqlRowCount(pack); if(rowCount == null) { - return new SQLResultSet(new JDBCBridge(pack)); + return new JDBCBridge(pack); } return rowCount.intValue(); } diff --git a/src/main/java/org/tarantool/jdbc/SQLConnection.java b/src/main/java/org/tarantool/jdbc/SQLConnection.java index 89b0f81d..f6334abe 100644 --- a/src/main/java/org/tarantool/jdbc/SQLConnection.java +++ b/src/main/java/org/tarantool/jdbc/SQLConnection.java @@ -1,5 +1,10 @@ package org.tarantool.jdbc; +import org.tarantool.CommunicationException; +import org.tarantool.JDBCBridge; +import org.tarantool.TarantoolConnection; +import org.tarantool.util.SQLStates; + import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -16,6 +21,8 @@ import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLNonTransientConnectionException; +import java.sql.SQLNonTransientException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; @@ -27,10 +34,6 @@ import java.util.Properties; import java.util.concurrent.Executor; -import org.tarantool.CommunicationException; -import org.tarantool.JDBCBridge; -import org.tarantool.TarantoolConnection; - import static org.tarantool.jdbc.SQLDriver.PROP_HOST; import static org.tarantool.jdbc.SQLDriver.PROP_PASSWORD; import static org.tarantool.jdbc.SQLDriver.PROP_PORT; @@ -39,9 +42,17 @@ @SuppressWarnings("Since15") public class SQLConnection implements Connection { + + private static final int UNSET_HOLDABILITY = 0; + private final TarantoolConnection connection; - final String url; - final Properties properties; + + private final String url; + private final Properties properties; + + private DatabaseMetaData cachedMetadata; + + private int resultSetHoldability = UNSET_HOLDABILITY; SQLConnection(String url, Properties properties) throws SQLException { this.url = url; @@ -62,7 +73,7 @@ public class SQLConnection implements Connection { } } if (e instanceof SQLException) - throw (SQLException)e; + throw (SQLException) e; throw new SQLException("Couldn't initiate connection using " + SQLDriver.diagProperties(properties), e); } } @@ -70,12 +81,12 @@ public class SQLConnection implements Connection { /** * Provides a connected socket to be used to initialize a native tarantool * connection. - * + *

* The implementation assumes that {@link #properties} contains all the * necessary info extracted from both the URI and connection properties * provided by the user. However, the overrides are free to also use the * {@link #url} if required. - * + *

* A connect is guarded with user provided timeout. Socket is configured * to honor this timeout for the following read/write operations as well. * @@ -111,7 +122,7 @@ protected Socket getConnectedSocket() throws SQLException { /** * Provides a newly connected socket instance. The method is intended to be * overridden to enable unit testing of the class. - * + *

* Not supposed to contain any logic other than a call to constructor. * * @return socket. @@ -123,11 +134,11 @@ protected Socket makeSocket() { /** * Provides a native tarantool connection instance. The method is intended * to be overridden to enable unit testing of the class. - * + *

* Not supposed to contain any logic other than a call to constructor. * - * @param user User name. - * @param pass Password. + * @param user User name. + * @param pass Password. * @param socket Connected socket. * @return Native tarantool connection. * @throws IOException if failed. @@ -140,14 +151,12 @@ protected TarantoolConnection makeConnection(String user, String pass, Socket so @Override public Statement createStatement() throws SQLException { - checkNotClosed(); - return new SQLStatement(this); + return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { - checkNotClosed(); - return new SQLPreparedStatement(this, sql); + return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } @Override @@ -196,7 +205,10 @@ public boolean isClosed() throws SQLException { @Override public DatabaseMetaData getMetaData() throws SQLException { checkNotClosed(); - return new SQLDatabaseMetadata(this); + if (cachedMetadata == null) { + cachedMetadata = new SQLDatabaseMetadata(this); + } + return cachedMetadata; } @Override @@ -242,13 +254,13 @@ public void clearWarnings() throws SQLException { @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { - throw new SQLFeatureNotSupportedException(); + return createStatement(resultSetType, resultSetConcurrency, getHoldability()); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { - throw new SQLFeatureNotSupportedException(); + return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability()); } @Override @@ -268,12 +280,18 @@ public void setTypeMap(Map> map) throws SQLException { @Override public void setHoldability(int holdability) throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + checkHoldabilitySupport(holdability); + resultSetHoldability = holdability; } @Override public int getHoldability() throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + if (resultSetHoldability == UNSET_HOLDABILITY) { + resultSetHoldability = getMetaData().getResultSetHoldability(); + } + return resultSetHoldability; } @Override @@ -297,15 +315,22 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { } @Override - public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Statement createStatement(int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + checkNotClosed(); + checkHoldabilitySupport(resultSetHoldability); + return new SQLStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability); } @Override - public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public PreparedStatement prepareStatement(String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + checkNotClosed(); + checkHoldabilitySupport(resultSetHoldability); + return new SQLPreparedStatement(this, sql, resultSetType, resultSetConcurrency, resultSetHoldability); } @Override @@ -423,16 +448,19 @@ public int getNetworkTimeout() throws SQLException { } @Override - public T unwrap(Class iface) throws SQLException { - throw new SQLFeatureNotSupportedException(); + public T unwrap(Class type) throws SQLException { + if (isWrapperFor(type)) { + return type.cast(this); + } + throw new SQLNonTransientException("Connection does not wrap " + type.getName()); } @Override - public boolean isWrapperFor(Class iface) throws SQLException { - throw new SQLFeatureNotSupportedException(); + public boolean isWrapperFor(Class type) throws SQLException { + return type.isAssignableFrom(this.getClass()); } - protected Object execute(String sql, Object ... args) throws SQLException { + protected Object execute(String sql, Object... args) throws SQLException { checkNotClosed(); try { return JDBCBridge.execute(connection, sql, args); @@ -442,17 +470,17 @@ protected Object execute(String sql, Object ... args) throws SQLException { } } - protected ResultSet executeQuery(String sql, Object ... args) throws SQLException { + protected JDBCBridge executeQuery(String sql, Object... args) throws SQLException { checkNotClosed(); try { - return new SQLResultSet(JDBCBridge.query(connection, sql, args)); + return JDBCBridge.query(connection, sql, args); } catch (Exception e) { handleException(e); throw new SQLException(formatError(sql, args), e); } } - protected int executeUpdate(String sql, Object ... args) throws SQLException { + protected int executeUpdate(String sql, Object... args) throws SQLException { checkNotClosed(); try { return JDBCBridge.update(connection, sql, args); @@ -463,7 +491,7 @@ protected int executeUpdate(String sql, Object ... args) throws SQLException { } protected List nativeSelect(Integer space, Integer index, List key, int offset, int limit, int iterator) - throws SQLException { + throws SQLException { checkNotClosed(); try { return connection.select(space, index, key, offset, limit, iterator); @@ -482,7 +510,18 @@ protected String getServerVersion() { */ protected void checkNotClosed() throws SQLException { if (isClosed()) - throw new SQLException("Connection is closed."); + throw new SQLNonTransientConnectionException( + "Connection is closed.", + SQLStates.CONNECTION_DOES_NOT_EXIST.getSqlState() + ); + } + + String getUrl() { + return url; + } + + Properties getProperties() { + return properties; } /** @@ -492,7 +531,7 @@ protected void checkNotClosed() throws SQLException { */ private void handleException(Exception e) { if (CommunicationException.class.isAssignableFrom(e.getClass()) || - IOException.class.isAssignableFrom(e.getClass())) { + IOException.class.isAssignableFrom(e.getClass())) { try { close(); } catch (SQLException ignored) { @@ -501,14 +540,31 @@ private void handleException(Exception e) { } } + /** + * Checks whether holdability is supported + * + * @param holdability param to be checked + * @throws SQLFeatureNotSupportedException param is not supported + * @throws SQLNonTransientException param has invalid value + */ + private void checkHoldabilitySupport(int holdability) throws SQLException { + if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT + && holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) { + throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState()); + } + if (!getMetaData().supportsResultSetHoldability(holdability)) { + throw new SQLFeatureNotSupportedException(); + } + } + /** * Provides error message that contains parameters of failed SQL statement. * - * @param sql SQL Text. + * @param sql SQL Text. * @param params Parameters of the SQL statement. * @return Formatted error message. */ - private static String formatError(String sql, Object ... params) { + private static String formatError(String sql, Object... params) { return "Failed to execute SQL: " + sql + ", params: " + Arrays.deepToString(params); } } diff --git a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java index 54c3d2df..d8f3c2bf 100644 --- a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java +++ b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java @@ -1,5 +1,8 @@ package org.tarantool.jdbc; +import org.tarantool.JDBCBridge; +import org.tarantool.Version; + import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -14,11 +17,9 @@ import java.util.List; import java.util.Map; -import org.tarantool.Version; -import org.tarantool.JDBCBridge; - @SuppressWarnings("Since15") public class SQLDatabaseMetadata implements DatabaseMetaData { + protected static final int _VSPACE = 281; protected static final int _VINDEX = 289; protected static final int SPACES_MAX = 65535; @@ -28,16 +29,15 @@ public class SQLDatabaseMetadata implements DatabaseMetaData { public static final int SPACE_ID_IDX = 0; protected final SQLConnection connection; - protected class SQLNullResultSet extends SQLResultSet { - public SQLNullResultSet(JDBCBridge bridge) { - super(bridge); + public SQLNullResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws SQLException { + super(bridge, ownerStatement); } @Override protected Object getRaw(int columnIndex) { - return columnIndex > row.size() ? null : row.get(columnIndex - 1); + return columnIndex > getCurrentRow().size() ? null : getCurrentRow().get(columnIndex - 1); } @Override @@ -65,12 +65,12 @@ public boolean allTablesAreSelectable() throws SQLException { @Override public String getURL() throws SQLException { - return connection.url; + return connection.getUrl(); } @Override public String getUserName() throws SQLException { - return connection.properties.getProperty("user"); + return connection.getProperties().getProperty("user"); } @Override @@ -646,13 +646,13 @@ public boolean dataDefinitionIgnoredInTransactions() throws SQLException { @Override public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } protected boolean like(String value, String[] parts) { @@ -676,7 +676,7 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam try { if (types != null && !Arrays.asList(types).contains("TABLE")) { connection.checkNotClosed(); - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } String[] parts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%"); List> spaces = (List>) connection.nativeSelect(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0); @@ -692,10 +692,16 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam rows.add(Arrays.asList((Object) tableName, (Object) "TABLE")); } } - return new SQLNullResultSet(JDBCBridge.mock(Arrays.asList("TABLE_NAME", "TABLE_TYPE", + List columnNames = Arrays.asList( + "TABLE_NAME", "TABLE_TYPE", //nulls - "REMARKS", "TABLE_CAT", "TABLE_SCHEM", "TABLE_TYPE", "TYPE_CAT", "TYPE_SCHEM", - "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"), rows)); + "REMARKS", "TABLE_CAT", + "TABLE_SCHEM", "TABLE_TYPE", + "TYPE_CAT", "TYPE_SCHEM", + "TYPE_NAME", "SELF_REFERENCING_COL_NAME", + "REF_GENERATION" + ); + return sqlNullResultSet(columnNames, rows); } catch (Exception e) { throw new SQLException("Failed to retrieve table(s) description: " + "tableNamePattern=\"" + tableNamePattern + "\".", e); @@ -707,10 +713,6 @@ public ResultSet getSchemas() throws SQLException { return rowOfNullsResultSet(); } - private SQLNullResultSet rowOfNullsResultSet() { - return new SQLNullResultSet(JDBCBridge.mock(Collections.emptyList(), Collections.singletonList(Collections.emptyList()))); - } - @Override public ResultSet getCatalogs() throws SQLException { return rowOfNullsResultSet(); @@ -718,7 +720,7 @@ public ResultSet getCatalogs() throws SQLException { @Override public ResultSet getTableTypes() throws SQLException { - return new SQLResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.asList("TABLE")))); + return asMetadataResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.asList("TABLE")))); } @Override @@ -748,12 +750,25 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa } } - return new SQLNullResultSet((JDBCBridge.mock( - 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", - //nulls - "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" - ), - rows))); + List columnNames = 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", + //nulls + "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" + ); + return sqlNullResultSet( + columnNames, + rows); } catch (Exception e) { throw new SQLException("Error processing table column metadata: " + "tableNamePattern=\"" + tableNamePattern + "\"; " + @@ -776,12 +791,12 @@ public ResultSet getTablePrivileges(String catalog, String schemaPattern, String @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override @@ -830,7 +845,7 @@ public int compare(List row0, List row1) { return col0.compareTo(col1); } }); - return new SQLNullResultSet((JDBCBridge.mock(colNames, rows))); + return sqlNullResultSet(colNames, rows); } catch (Exception e) { throw new SQLException("Error processing metadata for table \"" + table + "\".", e); } @@ -838,29 +853,29 @@ public int compare(List row0, List row1) { @Override public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getTypeInfo() throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override @@ -926,7 +941,7 @@ public boolean supportsBatchUpdates() throws SQLException { @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override @@ -956,28 +971,34 @@ public boolean supportsGetGeneratedKeys() throws SQLException { @Override public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } + /** + * {@inheritDoc} + * + * Support of {@link ResultSet#CLOSE_CURSORS_AT_COMMIT} is not + * available now because it requires cursor transaction support. + */ @Override public boolean supportsResultSetHoldability(int holdability) throws SQLException { - return false; + return holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override public int getResultSetHoldability() throws SQLException { - return 0; + return ResultSet.HOLD_CURSORS_OVER_COMMIT; } @Override @@ -1037,25 +1058,25 @@ public boolean autoCommitFailureClosesAllResultSets() throws SQLException { @Override public ResultSet getClientInfoProperties() throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { - return new SQLResultSet(JDBCBridge.EMPTY); + return asMetadataResultSet(JDBCBridge.EMPTY); } @Override @@ -1073,6 +1094,14 @@ public boolean isWrapperFor(Class iface) throws SQLException { throw new SQLFeatureNotSupportedException(); } + private ResultSet asMetadataResultSet(JDBCBridge jdbcBridge) throws SQLException { + return createMetadataStatement().executeMetadata(jdbcBridge); + } + + private SQLStatement createMetadataStatement() throws SQLException { + return connection.createStatement().unwrap(SQLStatement.class); + } + private static T ensureType(Class cls, Object v) throws Exception { if (v == null || !cls.isAssignableFrom(v.getClass())) { throw new Exception(String.format("Wrong value type '%s', expected '%s'.", @@ -1085,8 +1114,16 @@ private static T checkType(Class cls, Object v) { return (v != null && cls.isAssignableFrom(v.getClass())) ? cls.cast(v) : null; } - private ResultSet emptyResultSet(List colNames) { - return new SQLNullResultSet((JDBCBridge.mock(colNames, Collections.>emptyList()))); + private SQLNullResultSet rowOfNullsResultSet() throws SQLException { + return sqlNullResultSet(Collections.emptyList(), Collections.emptyList()); + } + + private SQLNullResultSet emptyResultSet(List colNames) throws SQLException { + return sqlNullResultSet(colNames, Collections.emptyList()); + } + + private SQLNullResultSet sqlNullResultSet(List colNames, List> rows) throws SQLException { + return new SQLNullResultSet(JDBCBridge.mock(colNames, rows), createMetadataStatement()); } } diff --git a/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java b/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java index 99b51370..f0748c06 100644 --- a/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java +++ b/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java @@ -25,21 +25,33 @@ import java.util.Map; public class SQLPreparedStatement extends SQLStatement implements PreparedStatement { + final static String INVALID_CALL_MSG = "The method cannot be called on a PreparedStatement."; final String sql; final Map params; - public SQLPreparedStatement(SQLConnection connection, String sql) { + public SQLPreparedStatement(SQLConnection connection, String sql) throws SQLException { super(connection); this.sql = sql; - this.params = new HashMap(); + this.params = new HashMap<>(); + } + + public SQLPreparedStatement(SQLConnection connection, + String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + super(connection, resultSetType, resultSetConcurrency, resultSetHoldability); + this.sql = sql; + this.params = new HashMap<>(); } @Override public ResultSet executeQuery() throws SQLException { + checkNotClosed(); discardLastResults(); - return connection.executeQuery(sql, getParams()); + return createResultSet(connection.executeQuery(sql, getParams())); } protected Object[] getParams() throws SQLException { @@ -56,93 +68,94 @@ protected Object[] getParams() throws SQLException { @Override public int executeUpdate() throws SQLException { + checkNotClosed(); discardLastResults(); return connection.executeUpdate(sql, getParams()); } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { - params.put(parameterIndex, null); + setParameter(parameterIndex, null); } @Override - public void setBoolean(int parameterIndex, boolean x) throws SQLException { - params.put(parameterIndex, x); + public void setBoolean(int parameterIndex, boolean parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setByte(int parameterIndex, byte x) throws SQLException { - params.put(parameterIndex, x); + public void setByte(int parameterIndex, byte parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setShort(int parameterIndex, short x) throws SQLException { - params.put(parameterIndex, x); + public void setShort(int parameterIndex, short parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setInt(int parameterIndex, int x) throws SQLException { - params.put(parameterIndex, x); + public void setInt(int parameterIndex, int parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setLong(int parameterIndex, long x) throws SQLException { - params.put(parameterIndex, x); + public void setLong(int parameterIndex, long parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setFloat(int parameterIndex, float x) throws SQLException { - params.put(parameterIndex, x); + public void setFloat(int parameterIndex, float parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setDouble(int parameterIndex, double x) throws SQLException { - params.put(parameterIndex, x); + public void setDouble(int parameterIndex, double parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { - params.put(parameterIndex, x); + public void setBigDecimal(int parameterIndex, BigDecimal parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setString(int parameterIndex, String x) throws SQLException { - params.put(parameterIndex, x); + public void setString(int parameterIndex, String parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setBytes(int parameterIndex, byte[] x) throws SQLException { - params.put(parameterIndex, x); + public void setBytes(int parameterIndex, byte[] parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setDate(int parameterIndex, Date x) throws SQLException { - params.put(parameterIndex, x); + public void setDate(int parameterIndex, Date parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setTime(int parameterIndex, Time x) throws SQLException { - params.put(parameterIndex, x); + public void setTime(int parameterIndex, Time parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { - params.put(parameterIndex, x); + public void setTimestamp(int parameterIndex, Timestamp parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { - params.put(parameterIndex, x); + public void setAsciiStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { - params.put(parameterIndex, x); + public void setUnicodeStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { - params.put(parameterIndex, x); + public void setBinaryStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override @@ -152,16 +165,22 @@ public void clearParameters() throws SQLException { @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { - params.put(parameterIndex, x); + setObject(parameterIndex, x, targetSqlType, -1); } @Override - public void setObject(int parameterIndex, Object x) throws SQLException { - params.put(parameterIndex, x); + public void setObject(int parameterIndex, Object value) throws SQLException { + setParameter(parameterIndex, value); + } + + private void setParameter(int parameterIndex, Object value) throws SQLException { + checkNotClosed(); + params.put(parameterIndex, value); } @Override public boolean execute() throws SQLException { + checkNotClosed(); discardLastResults(); return handleResult(connection.execute(sql, getParams())); } @@ -197,28 +216,28 @@ public ResultSetMetaData getMetaData() throws SQLException { } @Override - public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { - params.put(parameterIndex, x); + public void setDate(int parameterIndex, Date parameterValue, Calendar calendar) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { - params.put(parameterIndex, x); + public void setTime(int parameterIndex, Time parameterValue, Calendar calendar) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override - public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { - params.put(parameterIndex, x); + public void setTimestamp(int parameterIndex, Timestamp parameterValue, Calendar calendar) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { - params.put(parameterIndex, null); + setParameter(parameterIndex, null); } @Override - public void setURL(int parameterIndex, URL x) throws SQLException { - params.put(parameterIndex, x.toString()); + public void setURL(int parameterIndex, URL parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue.toString()); } @Override @@ -232,8 +251,8 @@ public void setRowId(int parameterIndex, RowId x) throws SQLException { } @Override - public void setNString(int parameterIndex, String value) throws SQLException { - params.put(parameterIndex, value); + public void setNString(int parameterIndex, String parameterValue) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override @@ -267,8 +286,8 @@ public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException } @Override - public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { - params.put(parameterIndex, x); + public void setObject(int parameterIndex, Object parameterValue, int targetSqlType, int scaleOrLength) throws SQLException { + setParameter(parameterIndex, parameterValue); } @Override diff --git a/src/main/java/org/tarantool/jdbc/SQLResultSet.java b/src/main/java/org/tarantool/jdbc/SQLResultSet.java index 249ab128..f3a54566 100644 --- a/src/main/java/org/tarantool/jdbc/SQLResultSet.java +++ b/src/main/java/org/tarantool/jdbc/SQLResultSet.java @@ -1,5 +1,7 @@ package org.tarantool.jdbc; +import org.tarantool.JDBCBridge; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Reader; @@ -19,6 +21,7 @@ import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLNonTransientException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Statement; @@ -29,26 +32,46 @@ import java.util.ListIterator; import java.util.Map; -import org.tarantool.JDBCBridge; - @SuppressWarnings("Since15") public class SQLResultSet implements ResultSet { - ListIterator> iterator; - final JDBCBridge bridge; - final SQLResultSetMetaData metaData; - int maxRows; - List row = null; + private ListIterator> iterator; + private JDBCBridge bridge; + private final SQLResultSetMetaData metaData; + + private final Statement statement; + private int maxRows; + private List row = null; + private final int type; + private final int concurrencyLevel; + private final int holdability; - public SQLResultSet(JDBCBridge bridge) { + public SQLResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws SQLException { this.bridge = bridge; iterator = bridge.iterator(); metaData = new SQLResultSetMetaData(bridge); + statement = ownerStatement; + type = statement.getResultSetType(); + concurrencyLevel = statement.getResultSetConcurrency(); + holdability = statement.getResultSetHoldability(); + } + + public int getMaxRows() { + return maxRows; + } + + public void setMaxRows(int maxRows) { + this.maxRows = maxRows; + } + + List getCurrentRow() { + return row; } @Override public boolean next() throws SQLException { + checkNotClosed(); if (iterator.hasNext() && (maxRows == 0 || iterator.nextIndex() < maxRows)) { row = iterator.next(); return true; @@ -300,32 +323,38 @@ public BigDecimal getBigDecimal(String columnLabel) throws SQLException { @Override public boolean isBeforeFirst() throws SQLException { + checkNotClosed(); return row == null && iterator.previousIndex() == -1; } @Override public boolean isAfterLast() throws SQLException { + checkNotClosed(); return iterator.nextIndex() == bridge.size() && row == null; } @Override public boolean isFirst() throws SQLException { + checkNotClosed(); return iterator.previousIndex() == 0; } @Override public boolean isLast() throws SQLException { + checkNotClosed(); return iterator.nextIndex() == bridge.size(); } @Override public void beforeFirst() throws SQLException { + checkNotClosed(); row = null; iterator = bridge.iterator(); } @Override public void afterLast() throws SQLException { + checkNotClosed(); while (next()) { } } @@ -338,6 +367,7 @@ public boolean first() throws SQLException { @Override public boolean last() throws SQLException { + checkNotClosed(); while (iterator.hasNext()) { next(); } @@ -346,6 +376,7 @@ public boolean last() throws SQLException { @Override public int getRow() throws SQLException { + checkNotClosed(); return iterator.previousIndex() + 1; } @@ -361,6 +392,7 @@ public boolean absolute(int row) throws SQLException { @Override public boolean relative(int rows) throws SQLException { + checkNotClosed(); for (int i = 0; i < rows && iterator.hasNext(); i++) { next(); } @@ -369,6 +401,7 @@ public boolean relative(int rows) throws SQLException { @Override public boolean previous() throws SQLException { + checkNotClosed(); if (iterator.hasPrevious()) { iterator.previous(); return true; @@ -378,6 +411,7 @@ public boolean previous() throws SQLException { @Override public void setFetchDirection(int direction) throws SQLException { + checkNotClosed(); if (direction != ResultSet.FETCH_FORWARD) { throw new SQLException("TYPE_FORWARD_ONLY"); } @@ -385,6 +419,7 @@ public void setFetchDirection(int direction) throws SQLException { @Override public int getFetchDirection() throws SQLException { + checkNotClosed(); return ResultSet.FETCH_FORWARD; } @@ -400,12 +435,14 @@ public int getFetchSize() throws SQLException { @Override public int getType() throws SQLException { - return ResultSet.TYPE_FORWARD_ONLY; + checkNotClosed(); + return type; } @Override public int getConcurrency() throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + return concurrencyLevel; } @Override @@ -813,12 +850,13 @@ public void updateRowId(String columnLabel, RowId x) throws SQLException { @Override public int getHoldability() throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + return holdability; } @Override public boolean isClosed() throws SQLException { - return false; + return statement.isClosed(); } @Override @@ -1059,4 +1097,11 @@ public String toString() { ", row=" + row + '}'; } + + protected void checkNotClosed() throws SQLException { + if (isClosed()) { + throw new SQLNonTransientException("ResultSet is closed."); + } + } + } diff --git a/src/main/java/org/tarantool/jdbc/SQLStatement.java b/src/main/java/org/tarantool/jdbc/SQLStatement.java index 3e75694a..658d2487 100644 --- a/src/main/java/org/tarantool/jdbc/SQLStatement.java +++ b/src/main/java/org/tarantool/jdbc/SQLStatement.java @@ -1,31 +1,55 @@ package org.tarantool.jdbc; +import org.tarantool.JDBCBridge; + import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLNonTransientException; import java.sql.SQLWarning; import java.sql.Statement; @SuppressWarnings("Since15") public class SQLStatement implements Statement { + protected final SQLConnection connection; + private SQLResultSet resultSet; + private final int resultSetType; + private final int resultSetConcurrency; + private final int resultSetHoldability; + private int updateCount; private int maxRows; - protected SQLStatement(SQLConnection sqlConnection) { + protected SQLStatement(SQLConnection sqlConnection) throws SQLException { + this.connection = sqlConnection; + this.resultSetType = ResultSet.TYPE_FORWARD_ONLY; + this.resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; + this.resultSetHoldability = sqlConnection.getHoldability(); + } + + protected SQLStatement(SQLConnection sqlConnection, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { this.connection = sqlConnection; + this.resultSetType = resultSetType; + this.resultSetConcurrency = resultSetConcurrency; + this.resultSetHoldability = resultSetHoldability; } @Override public ResultSet executeQuery(String sql) throws SQLException { + checkNotClosed(); discardLastResults(); - return connection.executeQuery(sql); + return createResultSet(connection.executeQuery(sql)); } @Override public int executeUpdate(String sql) throws SQLException { + checkNotClosed(); discardLastResults(); return connection.executeUpdate(sql); } @@ -47,15 +71,20 @@ public void setMaxFieldSize(int max) throws SQLException { @Override public int getMaxRows() throws SQLException { + checkNotClosed(); return maxRows; } @Override - public void setMaxRows(int max) throws SQLException { - maxRows = max; - if(resultSet!=null) { - resultSet.maxRows = maxRows; - } + public void setMaxRows(int maxRows) throws SQLException { + checkNotClosed(); + if (maxRows < 0) { + throw new SQLNonTransientException("Max rows parameter can't be a negative value"); + } + this.maxRows = maxRows; + if (resultSet != null) { + resultSet.setMaxRows(this.maxRows); + } } @Override @@ -95,12 +124,14 @@ public void setCursorName(String name) throws SQLException { @Override public boolean execute(String sql) throws SQLException { + checkNotClosed(); discardLastResults(); return handleResult(connection.execute(sql)); } @Override public ResultSet getResultSet() throws SQLException { + checkNotClosed(); try { return resultSet; } finally { @@ -110,6 +141,7 @@ public ResultSet getResultSet() throws SQLException { @Override public int getUpdateCount() throws SQLException { + checkNotClosed(); try { return updateCount; } finally { @@ -119,11 +151,13 @@ public int getUpdateCount() throws SQLException { @Override public boolean getMoreResults() throws SQLException { + checkNotClosed(); return false; } @Override public void setFetchDirection(int direction) throws SQLException { + checkNotClosed(); if (direction != ResultSet.FETCH_FORWARD) { throw new SQLFeatureNotSupportedException(); } @@ -131,11 +165,14 @@ public void setFetchDirection(int direction) throws SQLException { @Override public int getFetchDirection() throws SQLException { + checkNotClosed(); return ResultSet.FETCH_FORWARD; } @Override public void setFetchSize(int rows) throws SQLException { + checkNotClosed(); + // no-op } @Override @@ -145,12 +182,14 @@ public int getFetchSize() throws SQLException { @Override public int getResultSetConcurrency() throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + return resultSetConcurrency; } @Override public int getResultSetType() throws SQLException { - return ResultSet.TYPE_FORWARD_ONLY; + checkNotClosed(); + return resultSetType; } @Override @@ -175,6 +214,7 @@ public Connection getConnection() throws SQLException { @Override public boolean getMoreResults(int current) throws SQLException { + checkNotClosed(); return false; } @@ -215,7 +255,8 @@ public boolean execute(String sql, String[] columnNames) throws SQLException { @Override public int getResultSetHoldability() throws SQLException { - throw new SQLFeatureNotSupportedException(); + checkNotClosed(); + return resultSetHoldability; } @Override @@ -240,17 +281,21 @@ public void closeOnCompletion() throws SQLException { @Override public boolean isCloseOnCompletion() throws SQLException { + checkNotClosed(); return false; } @Override - public T unwrap(Class iface) throws SQLException { - throw new SQLFeatureNotSupportedException(); + public T unwrap(Class type) throws SQLException { + if (isWrapperFor(type)) { + return type.cast(this); + } + throw new SQLNonTransientException("Statement does not wrap " + type.getName()); } @Override - public boolean isWrapperFor(Class iface) throws SQLException { - throw new SQLFeatureNotSupportedException(); + public boolean isWrapperFor(Class type) throws SQLException { + return type.isAssignableFrom(this.getClass()); } /** @@ -274,10 +319,10 @@ protected void discardLastResults() { * @param result The result of SQL statement execution. * @return {@code true}, if the result is a ResultSet object. */ - protected boolean handleResult(Object result) { - if (result instanceof SQLResultSet) { - resultSet = (SQLResultSet) result; - resultSet.maxRows = maxRows; + protected boolean handleResult(Object result) throws SQLException { + if (result instanceof JDBCBridge) { + resultSet = createResultSet((JDBCBridge) result); + resultSet.setMaxRows(maxRows); updateCount = -1; return true; } else { @@ -286,4 +331,27 @@ protected boolean handleResult(Object result) { return false; } } + + /** + * Returns {@link ResultSet} which will be initialized by data + * + * @param data predefined result to be wrapped by {@link ResultSet} + * @return wrapped result + * @throws SQLException if a database access error occurs or + * this method is called on a closed Statement + */ + public ResultSet executeMetadata(JDBCBridge data) throws SQLException { + checkNotClosed(); + return createResultSet(data); + } + + protected SQLResultSet createResultSet(JDBCBridge result) throws SQLException { + return new SQLResultSet(result, this); + } + + protected void checkNotClosed() throws SQLException { + if (isClosed()) { + throw new SQLNonTransientException("Statement is closed."); + } + } } diff --git a/src/main/java/org/tarantool/util/SQLStates.java b/src/main/java/org/tarantool/util/SQLStates.java new file mode 100644 index 00000000..48f7f332 --- /dev/null +++ b/src/main/java/org/tarantool/util/SQLStates.java @@ -0,0 +1,17 @@ +package org.tarantool.util; + +public enum SQLStates { + + INVALID_PARAMETER_VALUE("22023"), + CONNECTION_DOES_NOT_EXIST("08003"); + + private final String sqlState; + + SQLStates(String sqlState) { + this.sqlState = sqlState; + } + + public String getSqlState() { + return sqlState; + } +} diff --git a/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java b/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java index a6a05e88..270eb6a8 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java @@ -8,7 +8,9 @@ import java.net.Socket; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -20,6 +22,7 @@ @SuppressWarnings("Since15") public class JdbcConnectionIT extends AbstractJdbcIT { + @Test public void testCreateStatement() throws SQLException { Statement stmt = conn.createStatement(); @@ -71,7 +74,7 @@ public void execute() throws Throwable { Field sock = TarantoolConnection.class.getDeclaredField("socket"); sock.setAccessible(true); - assertEquals(3000, ((Socket)sock.get(tntCon.get(conn))).getSoTimeout()); + assertEquals(3000, ((Socket) sock.get(tntCon.get(conn))).getSoTimeout()); } @Test @@ -85,15 +88,20 @@ public void testClosedConnection() throws SQLException { @Override public void execute() throws Throwable { switch (step) { - case 0: conn.createStatement(); + case 0: + conn.createStatement(); break; - case 1: conn.prepareStatement("TEST"); + case 1: + conn.prepareStatement("TEST"); break; - case 2: conn.getMetaData(); + case 2: + conn.getMetaData(); break; - case 3: conn.getNetworkTimeout(); + case 3: + conn.getNetworkTimeout(); break; - case 4: conn.setNetworkTimeout(null, 1000); + case 4: + conn.setNetworkTimeout(null, 1000); break; default: fail(); @@ -104,4 +112,84 @@ public void execute() throws Throwable { } assertEquals(5, i); } -} \ No newline at end of file + + @Test + public void testConnectionUnwrap() throws SQLException { + assertEquals(conn, conn.unwrap(SQLConnection.class)); + assertThrows(SQLException.class, () -> conn.unwrap(Integer.class)); + } + + @Test + public void testConnectionIsWrapperFor() throws SQLException { + assertTrue(conn.isWrapperFor(SQLConnection.class)); + assertFalse(conn.isWrapperFor(Integer.class)); + } + + @Test + public void testDefaultGetHoldability() throws SQLException { + // default connection holdability should be equal to metadata one + assertEquals(conn.getMetaData().getResultSetHoldability(), conn.getHoldability()); + } + + @Test + public void testSetAndGetHoldability() throws SQLException { + conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, conn.getHoldability()); + + assertThrows(SQLFeatureNotSupportedException.class, () -> conn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT)); + assertThrows(SQLException.class, () -> conn.setHoldability(Integer.MAX_VALUE)); + + assertThrows(SQLException.class, () -> { + conn.close(); + conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); + }); + } + + @Test + public void testCreateHoldableStatement() throws SQLException { + Statement statement = conn.createStatement(); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + assertThrows(SQLException.class, () -> { + conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, Integer.MAX_VALUE); + }); + assertThrows(SQLFeatureNotSupportedException.class, () -> { + conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); + }); + assertThrows(SQLException.class, () -> { + conn.close(); + conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + }); + } + + @Test + public void testPrepareHoldableStatement() throws SQLException { + String sqlString = "TEST"; + Statement statement = conn.prepareStatement(sqlString); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + statement = conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + statement = conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability()); + + assertThrows(SQLException.class, () -> { + conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, Integer.MAX_VALUE); + }); + assertThrows(SQLFeatureNotSupportedException.class, () -> { + conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); + }); + assertThrows(SQLException.class, () -> { + conn.close(); + conn.prepareStatement(sqlString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + }); + } + +} diff --git a/src/test/java/org/tarantool/jdbc/JdbcDatabaseMetaDataIT.java b/src/test/java/org/tarantool/jdbc/JdbcDatabaseMetaDataIT.java index 8236ba0a..dcf90126 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcDatabaseMetaDataIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcDatabaseMetaDataIT.java @@ -1,13 +1,15 @@ package org.tarantool.jdbc; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -17,9 +19,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class JdbcDatabaseMetaDataIT extends AbstractJdbcIT { private DatabaseMetaData meta; @@ -239,4 +238,20 @@ public void testGetDriverNameVersion() throws SQLException { assertEquals(majorVersion, majorVersionMatched); assertEquals(minorVersion, minorVersionMatched); } + + @Test + public void testGetResultSetHoldability() throws SQLException { + int resultSetHoldability = meta.getResultSetHoldability(); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, resultSetHoldability); + } + + @Test + public void testSupportsResultSetHoldability() throws SQLException { + assertTrue(meta.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertFalse(meta.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT)); + assertFalse(meta.supportsResultSetHoldability(Integer.MAX_VALUE)); + assertFalse(meta.supportsResultSetHoldability(Integer.MIN_VALUE)); + assertFalse(meta.supportsResultSetHoldability(42)); + } + } diff --git a/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java b/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java index 8e117bf7..f3fb26ad 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java @@ -1,8 +1,8 @@ package org.tarantool.jdbc; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.function.Executable; import java.math.BigDecimal; @@ -157,7 +157,7 @@ public void execute() throws Throwable { } } }); - assertEquals("Connection is closed.", e.getMessage()); + assertEquals("Statement is closed.", e.getMessage()); } assertEquals(3, i); } diff --git a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java index d7c53e1d..4d8ac44c 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java @@ -1,10 +1,11 @@ package org.tarantool.jdbc; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -16,10 +17,12 @@ public class JdbcResultSetIT extends JdbcTypesIT { private Statement stmt; + private DatabaseMetaData metaData; @BeforeEach public void setUp() throws Exception { stmt = conn.createStatement(); + metaData = conn.getMetaData(); } @AfterEach @@ -121,4 +124,12 @@ public void testGetByteArrayColumn() throws SQLException { .setValues(BINARY_VALS) .testGetColumn(); } + + @Test + public void testHoldability() throws SQLException { + ResultSet resultSet = stmt.executeQuery("SELECT * FROM test WHERE id < 0"); + assertNotNull(resultSet); + assertEquals(metaData.getResultSetHoldability(), resultSet.getHoldability()); + } + } diff --git a/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java b/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java index 735f326d..44d8dea0 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java @@ -9,11 +9,11 @@ import java.sql.SQLException; import java.sql.Statement; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; public class JdbcStatementIT extends AbstractJdbcIT { @@ -89,7 +89,7 @@ public void execute() throws Throwable { } } }); - assertEquals("Connection is closed.", e.getMessage()); + assertEquals("Statement is closed.", e.getMessage()); } assertEquals(3, i); }