Skip to content

Commit

Permalink
jdbc: add support for a result set holdability
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dponomarev authored and Totktonada committed Mar 21, 2019
1 parent 9b3f91f commit 214b21a
Show file tree
Hide file tree
Showing 12 changed files with 544 additions and 187 deletions.
5 changes: 3 additions & 2 deletions src/main/java/org/tarantool/JDBCBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.tarantool.protocol.TarantoolPacket;

public class JDBCBridge {

public static final JDBCBridge EMPTY = new JDBCBridge(Collections.<TarantoolBase.SQLMetaData>emptyList(), Collections.<List<Object>>emptyList());

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

public static JDBCBridge mock(List<String> fields, List<List<Object>> values) {
List<TarantoolBase.SQLMetaData> meta = new ArrayList<TarantoolBase.SQLMetaData>(fields.size());
for(String field:fields) {
for(String field : fields) {
meta.add(new TarantoolBase.SQLMetaData(field));
}
return new JDBCBridge(meta, values);
Expand All @@ -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();
}
Expand Down
138 changes: 97 additions & 41 deletions src/main/java/org/tarantool/jdbc/SQLConnection.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -62,20 +73,20 @@ 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);
}
}

/**
* Provides a connected socket to be used to initialize a native tarantool
* connection.
*
* <p>
* 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.
*
* <p>
* A connect is guarded with user provided timeout. Socket is configured
* to honor this timeout for the following read/write operations as well.
*
Expand Down Expand Up @@ -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.
*
* <p>
* Not supposed to contain any logic other than a call to constructor.
*
* @return socket.
Expand All @@ -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.
*
* <p>
* 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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -268,12 +280,18 @@ public void setTypeMap(Map<String, Class<?>> 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
Expand All @@ -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
Expand Down Expand Up @@ -423,16 +448,19 @@ public int getNetworkTimeout() throws SQLException {
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLFeatureNotSupportedException();
public <T> T unwrap(Class<T> 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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
}

/**
Expand All @@ -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) {
Expand All @@ -501,14 +540,31 @@ private void handleException(Exception e) {
}
}

/**
* Checks whether <code>holdability</code> 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);
}
}
Loading

0 comments on commit 214b21a

Please sign in to comment.