diff --git a/src/main/org/firebirdsql/jdbc/FBConnection.java b/src/main/org/firebirdsql/jdbc/FBConnection.java index 93d874d03..e4684b5e8 100644 --- a/src/main/org/firebirdsql/jdbc/FBConnection.java +++ b/src/main/org/firebirdsql/jdbc/FBConnection.java @@ -858,13 +858,13 @@ public Savepoint setSavepoint() throws SQLException { * Set the savepoint on the server. * * @param savepoint - * savepoint to set. + * savepoint to set * @throws SQLException - * if something went wrong. + * if something went wrong */ private void setSavepoint(FBSavepoint savepoint) throws SQLException { if (getAutoCommit()) { - throw new SQLException("Connection.setSavepoint() method cannot be used in auto-commit mode.", + throw new SQLException("Connection.setSavepoint() method cannot be used in auto-commit mode", SQL_STATE_INVALID_TX_STATE); } @@ -874,10 +874,7 @@ private void setSavepoint(FBSavepoint savepoint) throws SQLException { txCoordinator.ensureTransaction(); - StringBuilder setSavepoint = new StringBuilder("SAVEPOINT "); - getQuoteStrategy().appendQuoted(savepoint.getServerSavepointId(), setSavepoint); - - getGDSHelper().executeImmediate(setSavepoint.toString()); + getGDSHelper().executeImmediate(savepoint.toSavepointStatement(getQuoteStrategy())); savepoints.add(savepoint); } @@ -921,26 +918,14 @@ public void rollback(Savepoint savepoint) throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); if (getAutoCommit()) { - throw new SQLException("Connection.rollback(Savepoint) method cannot be used in auto-commit mode.", + throw new SQLException("Connection.rollback(Savepoint) method cannot be used in auto-commit mode", SQL_STATE_INVALID_TX_STATE); - } - - // TODO The error message and actual condition do not match - if (!(savepoint instanceof FBSavepoint fbSavepoint)) { - throw new SQLException("Specified savepoint was not obtained from this connection."); - } - - if (mc.inDistributedTransaction()) { + } else if (mc.inDistributedTransaction()) { throw new SQLException("Connection enlisted in distributed transaction", SQL_STATE_INVALID_TX_STATE); } - if (!fbSavepoint.isValid()) { - throw new SQLException("Savepoint is no longer valid."); - } - - StringBuilder rollbackSavepoint = new StringBuilder("ROLLBACK TO "); - getQuoteStrategy().appendQuoted(fbSavepoint.getServerSavepointId(), rollbackSavepoint); - getGDSHelper().executeImmediate(rollbackSavepoint.toString()); + FBSavepoint fbSavepoint = validateSavepoint(savepoint); + getGDSHelper().executeImmediate(fbSavepoint.toRollbackStatement(getQuoteStrategy())); } } @@ -949,24 +934,12 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); if (getAutoCommit()) { - throw new SQLException("Connection.releaseSavepoint() method cannot be used in auto-commit mode.", + throw new SQLException("Connection.releaseSavepoint() method cannot be used in auto-commit mode", SQL_STATE_INVALID_TX_STATE); } - // TODO The error message and actual condition do not match - if (!(savepoint instanceof FBSavepoint fbSavepoint)) { - throw new SQLException("Specified savepoint was not obtained from this connection."); - } - - if (!fbSavepoint.isValid()) { - throw new SQLException("Savepoint is no longer valid."); - } - - StringBuilder rollbackSavepoint = new StringBuilder("RELEASE SAVEPOINT "); - getQuoteStrategy().appendQuoted(fbSavepoint.getServerSavepointId(), rollbackSavepoint).append(" ONLY"); - getGDSHelper().executeImmediate(rollbackSavepoint.toString()); - - fbSavepoint.invalidate(); + FBSavepoint fbSavepoint = validateSavepoint(savepoint); + getGDSHelper().executeImmediate(fbSavepoint.toReleaseStatement(getQuoteStrategy())); savepoints.remove(fbSavepoint); } @@ -977,14 +950,23 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { */ protected void invalidateSavepoints() { try (LockCloseable ignored = withLock()) { - for (FBSavepoint savepoint : savepoints) { - savepoint.invalidate(); - } - savepoints.clear(); } } + FBSavepoint validateSavepoint(Savepoint savepoint) throws SQLException { + // TODO The error message and actual condition do not match + if (!(savepoint instanceof FBSavepoint fbSavepoint)) { + throw new SQLException("Specified savepoint was not obtained from this connection"); + } + + if (!savepoints.contains(fbSavepoint)) { + throw new SQLException("Savepoint is no longer valid"); + } + + return fbSavepoint; + } + /** * Returns a FBLocalTransaction instance that enables a component to * demarcate resource manager local transactions on this connection. diff --git a/src/main/org/firebirdsql/jdbc/FBSavepoint.java b/src/main/org/firebirdsql/jdbc/FBSavepoint.java index 1ef45e275..ba82bcad2 100644 --- a/src/main/org/firebirdsql/jdbc/FBSavepoint.java +++ b/src/main/org/firebirdsql/jdbc/FBSavepoint.java @@ -23,123 +23,89 @@ /** * Savepoint implementation. * + * @param savepointId + * numeric savepoint id (must be non-{@code null} if {@code name} is {@code null}). + * @param name + * savepoint name (must be non-{@code null} if {@code savepointId} is {@code null}) * @author Roman Rokytskyy + * @author Mark Rotteveel */ -public final class FBSavepoint implements FirebirdSavepoint { +public record FBSavepoint(Integer savepointId, String name) implements FirebirdSavepoint { private static final String SAVEPOINT_ID_PREFIX = "SVPT"; - private final boolean named; - private final int savepointId; - private final String name; - private boolean valid = true; + public FBSavepoint { + if (savepointId == null && name == null) { + throw new NullPointerException("savepointId and name cannot both be null"); + } else if (name == null) { + name = generateSavepointName(savepointId); + } else if (savepointId != null) { + throw new IllegalArgumentException("savepointId cannot be non-null if name is not null"); + } else if (name.isBlank()) { + throw new IllegalArgumentException("name must be non-blank"); + } + } /** - * Create instance of this class. + * Create an unnamed savepoint. * * @param savepointId - * ID of the savepoint. + * ID of the savepoint */ public FBSavepoint(int savepointId) { - this(false, savepointId, getSavepointServerId(savepointId)); + this(savepointId, null); } /** - * Create instance of this class for the specified name. + * Create a named savepoint. * * @param name - * name of the savepoint. + * name of the savepoint */ public FBSavepoint(String name) { - this(true, -1, name); - } - - private FBSavepoint(boolean named, int savepointId, String name) { - this.named = named; - this.savepointId = savepointId; - this.name = name; + this(null, name); } /** * Generate a savepoint name for the specified savepoint id. * * @param savePointId - * savepoint id. - * @return valid savepoint name. + * savepoint id + * @return valid savepoint name */ - private static String getSavepointServerId(int savePointId) { + private static String generateSavepointName(int savePointId) { if (savePointId >= 0) { return SAVEPOINT_ID_PREFIX + savePointId; } return SAVEPOINT_ID_PREFIX + '_' + Math.abs(savePointId); } - /** - * Get server savepoint name. - * <p> - * This method generates correct name for the savepoint that can be used in the SQL statement after - * dialect-appropriate quoting. - * </p> - * - * @return valid server-side name for the savepoint. - */ - String getServerSavepointId() { - return name; - } - @Override public int getSavepointId() throws SQLException { - if (named) { - throw new SQLException("Savepoint is named."); + if (savepointId == null) { + throw new SQLException("Savepoint is named"); } return savepointId; } @Override public String getSavepointName() throws SQLException { - if (!named) { - throw new SQLException("Savepoint is unnamed."); + if (savepointId != null) { + throw new SQLException("Savepoint is unnamed"); } return name; } - /** - * Check if the savepoint is valid. - * - * @return {@code true} if savepoint is valid. - */ - boolean isValid() { - return valid; - } - - /** - * Make this savepoint invalid. - */ - void invalidate() { - this.valid = false; + String toSavepointStatement(QuoteStrategy quoteStrategy) { + return "SAVEPOINT " + quoteStrategy.quoteObjectName(name); } - /** - * Check if objects are equal. For unnamed savepoints their IDs are checked, otherwise their names. - * - * @param obj - * object to test. - * @return {@code true} if {@code obj} is equal to this object. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (!(obj instanceof FBSavepoint)) return false; - - FBSavepoint that = (FBSavepoint) obj; - - return this.named == that.named && - this.name.equals(that.name); + String toRollbackStatement(QuoteStrategy quoteStrategy) { + return "ROLLBACK TO " + quoteStrategy.quoteObjectName(name); } - @Override - public int hashCode() { - return name.hashCode(); + String toReleaseStatement(QuoteStrategy quoteStrategy) { + return "RELEASE SAVEPOINT " + quoteStrategy.quoteObjectName(name) + " ONLY"; } } diff --git a/src/test/org/firebirdsql/jdbc/FBSavepointTest.java b/src/test/org/firebirdsql/jdbc/FBSavepointTest.java index acda17d8b..f40dab701 100644 --- a/src/test/org/firebirdsql/jdbc/FBSavepointTest.java +++ b/src/test/org/firebirdsql/jdbc/FBSavepointTest.java @@ -77,7 +77,7 @@ void testSavePointCreationNotAllowedInAutoCommit() throws Exception { connection.setAutoCommit(true); SQLException exception = assertThrows(SQLException.class, connection::setSavepoint); - assertThat(exception, message(equalTo("Connection.setSavepoint() method cannot be used in auto-commit mode."))); + assertThat(exception, message(equalTo("Connection.setSavepoint() method cannot be used in auto-commit mode"))); } @Test @@ -94,7 +94,7 @@ void getSavePointNameOnUnnamedSavePointThrowsException() throws Exception { Savepoint savepoint = connection.setSavepoint(); SQLException exception = assertThrows(SQLException.class, savepoint::getSavepointName); - assertThat(exception, message(equalTo("Savepoint is unnamed."))); + assertThat(exception, message(equalTo("Savepoint is unnamed"))); } /** @@ -136,7 +136,7 @@ void getSavePointIdOnNamedSavePointThrowsException() throws Exception { Savepoint savepoint = connection.setSavepoint("named"); SQLException exception = assertThrows(SQLException.class, savepoint::getSavepointId); - assertThat(exception, message(equalTo("Savepoint is named."))); + assertThat(exception, message(equalTo("Savepoint is named"))); } /**