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); }