Skip to content

Commit

Permalink
#811 Backport #806 to Jaybird 5 (Fix completion behaviour of statemen…
Browse files Browse the repository at this point in the history
…ts in auto-commit)
  • Loading branch information
mrotteveel committed Jul 6, 2024
1 parent 8f6ae8a commit 78815c0
Show file tree
Hide file tree
Showing 8 changed files with 469 additions and 204 deletions.
9 changes: 9 additions & 0 deletions src/docs/asciidoc/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ Changes per Jaybird 5 release.
See also <<whats-new-in-jaybird-5>>.
For known issues, consult <<known-issues>>.

[#jaybird-5-0-6-changelog]
=== Jaybird 5.0.6

The following has been changed or fixed since Jaybird 5.0.5:

* Fixed: Exceptions during statement execution did not always complete the statement, which could delay transaction commit in auto-commit mode (https://github.com/FirebirdSQL/jaybird/issues/806[#806])
+
This fix was backported from Jaybird 6.
[#jaybird-5-0-5-changelog]
=== Jaybird 5.0.5

Expand Down
128 changes: 62 additions & 66 deletions src/main/org/firebirdsql/jdbc/FBCallableStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.firebirdsql.jdbc.escape.FBEscapedCallParser;
import org.firebirdsql.jdbc.field.FBField;
import org.firebirdsql.jdbc.field.TypeConversionException;
import org.firebirdsql.util.Primitives;

import java.io.InputStream;
import java.io.Reader;
Expand Down Expand Up @@ -84,6 +83,7 @@ void close(boolean ignoreAlreadyClosed) throws SQLException {
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
// TODO See http://tracker.firebirdsql.org/browse/JDBC-352
notifyStatementStarted(false);
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));
Expand All @@ -95,63 +95,55 @@ public ParameterMetaData getParameterMetaData() throws SQLException {

@Override
public void addBatch() throws SQLException {
checkValidity();
try (LockCloseable ignored = withLock()) {
checkValidity();
procedureCall.checkParameters();
batchList.add((FBProcedureCall) procedureCall.clone());
}
}

@Override
public void clearBatch() throws SQLException {
checkValidity();

try (LockCloseable ignored = withLock()) {
checkValidity();
// TODO Find open streams and close them?
batchList.clear();
}
}

@Override
protected List<Long> executeBatchInternal() throws SQLException {
checkValidity();
try (LockCloseable ignored = withLock()) {
boolean success = false;
checkValidity();
List<Long> results = new ArrayList<>(batchList.size());
notifyStatementStarted();
try {
notifyStatementStarted();

List<Long> results = new ArrayList<>(batchList.size());
Iterator<FBProcedureCall> iterator = batchList.iterator();

try {
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));

while (iterator.hasNext()) {
procedureCall = iterator.next();
executeSingleForBatch(results);
}

success = true;
return results;
} catch (SQLException ex) {
throw createBatchUpdateException(ex.getMessage(), ex.getSQLState(),
ex.getErrorCode(), Primitives.toLongArray(results), ex);
} finally {
clearBatch();
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));
for (FBProcedureCall fbProcedureCall : batchList) {
procedureCall = fbProcedureCall;
results.add(executeSingleForBatch());
}
notifyStatementCompleted();
return results;
} catch (SQLException e) {
BatchUpdateException batchUpdateException = createBatchUpdateException(e, results);
notifyStatementCompleted(false, batchUpdateException);
throw batchUpdateException;
} catch (RuntimeException e) {
notifyStatementCompleted(false, e);
throw e;
} finally {
notifyStatementCompleted(success);
clearBatch();
}
}
}
private void executeSingleForBatch(List<Long> results) throws SQLException {

private long executeSingleForBatch() throws SQLException {
if (internalExecute(!isSelectableProcedure())) {
throw new SQLException("Statements executed as batch should not produce a result set",
SQLStateConstants.SQL_STATE_INVALID_STMT_TYPE);
throw batchStatementReturnedResultSet();
}

results.add(getLargeUpdateCountMinZero());
return getLargeUpdateCountMinZero();
}

@Override
Expand All @@ -170,7 +162,7 @@ public boolean isSelectableProcedure() {
* @throws SQLException if something went wrong.
*/
protected void setRequiredTypes() throws SQLException {
setRequiredTypesInternal((FBResultSet) (singletonRs != null ? singletonRs : getResultSet()));
setRequiredTypesInternal((FBResultSet) (singletonRs != null ? singletonRs : getResultSet(false)));
}

private void setRequiredTypesInternal(FBResultSet resultSet) throws SQLException {
Expand Down Expand Up @@ -216,72 +208,76 @@ public ResultSetMetaData getMetaData() throws SQLException {

@Override
public boolean execute() throws SQLException {
procedureCall.checkParameters();
boolean hasResultSet = false;
try (LockCloseable ignored = withLock()) {
checkValidity();
procedureCall.checkParameters();
notifyStatementStarted();

try {
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));
hasResultSet = internalExecute(!isSelectableProcedure());

boolean hasResultSet = internalExecute(!isSelectableProcedure());
if (hasResultSet) {
setRequiredTypes();
} else {
notifyStatementCompleted();
}
} finally {
if (!hasResultSet) notifyStatementCompleted();
return hasResultSet;
} catch (Exception e) {
notifyStatementCompleted(true, e);
throw e;
}

return hasResultSet;
}

}

//This method prepares statement before execution. Rest of the processing is done by superclass.
@Override
public ResultSet executeQuery() throws SQLException {
procedureCall.checkParameters();
try (LockCloseable ignored = withLock()) {
checkValidity();
procedureCall.checkParameters();
notifyStatementStarted();
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));

if (!internalExecute(!isSelectableProcedure()))
throw new FBSQLException("No resultset for sql", SQLStateConstants.SQL_STATE_NO_RESULT_SET);

ResultSet rs = getResultSet();
setRequiredTypesInternal((FBResultSet) rs);
return rs;
try {
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));
if (!internalExecute(!isSelectableProcedure())) {
throw queryProducedNoResultSet();
}
ResultSet rs = getResultSet(false);
setRequiredTypesInternal((FBResultSet) rs);
return rs;
} catch (Exception e) {
notifyStatementCompleted(true, e);
throw e;
}
}
}

// This method prepares statement before execution. Rest of the processing is done by superclass.
@Override
public int executeUpdate() throws SQLException {
procedureCall.checkParameters();
try (LockCloseable ignored = withLock()) {
checkValidity();
procedureCall.checkParameters();
notifyStatementStarted();
try {
notifyStatementStarted();
prepareFixedStatement(procedureCall.getSQL(isSelectableProcedure()));

/*
* // R.Rokytskyy: JDBC CTS suite uses executeUpdate() //
* together with output parameters, therefore we cannot //
* throw exception if we want to pass the test suite
*
* if (internalExecute(true)) throw new FBSQLException(
* "Update statement returned results.");
/* R.Rokytskyy: JDBC CTS suite uses executeUpdate() together with output parameters, therefore we
* cannot throw exception if we want to pass the test suite.
*
* if (internalExecute(true)) throw updateReturnedResultSet();
*/

boolean hasResults = internalExecute(!isSelectableProcedure());

if (hasResults) {
setRequiredTypes();
}

return getUpdateCountMinZero();
} finally {
notifyStatementCompleted();
int updateCount = getUpdateCountMinZero();
if (!isSelectableProcedure()) {
notifyStatementCompleted();
}
return updateCount;
} catch (Exception e) {
notifyStatementCompleted(true, e);
throw e;
}
}
}
Expand Down
Loading

0 comments on commit 78815c0

Please sign in to comment.