org.firebirdsql.jdbc.AbstractStatement Maven / Gradle / Ivy
Show all versions of jaybird Show documentation
/*
* Firebird Open Source JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc;
import org.firebirdsql.gds.ng.FbAttachment;
import org.firebirdsql.gds.ng.FbStatement;
import org.firebirdsql.gds.ng.LockCloseable;
import org.firebirdsql.util.InternalApi;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.sql.SQLException;
import java.sql.SQLNonTransientException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Objects.requireNonNull;
import static org.firebirdsql.jdbc.SQLStateConstants.SQL_STATE_INVALID_ATTR_VALUE;
import static org.firebirdsql.jdbc.SQLStateConstants.SQL_STATE_INVALID_STATEMENT_ID;
/**
* Common abstract base class for statement implementations.
*
* This class is internal API of Jaybird. Future versions may radically change, move, or make inaccessible this type.
* For the public API, refer to the {@link Statement} and {@link FirebirdStatement} interfaces.
*
*
* @author Mark Rotteveel
* @since 6
*/
@InternalApi
@NullMarked
public abstract class AbstractStatement implements Statement, FirebirdStatement {
private static final AtomicInteger STATEMENT_ID_GENERATOR = new AtomicInteger();
private final int localStatementId = STATEMENT_ID_GENERATOR.incrementAndGet();
protected final FBConnection connection;
private @Nullable String cursorName;
@SuppressWarnings("java:S3077")
private volatile @Nullable SQLWarning warning;
private FetchConfig fetchConfig;
private volatile boolean closed;
private boolean poolable;
private boolean closeOnCompletion;
protected AbstractStatement(FBConnection connection, ResultSetBehavior resultSetBehavior) {
this.connection = requireNonNull(connection, "connection");
fetchConfig = new FetchConfig(resultSetBehavior);
}
@Override
public final FBConnection getConnection() throws SQLException {
checkValidity();
return connection;
}
/**
* @return instance of {@link FbStatement} associated with this statement; cannot be {@code null}
* @throws SQLException
* if this statement is closed
* @throws java.sql.SQLFeatureNotSupportedException
* if this statement implementation does not use statement handles
*/
protected abstract FbStatement getStatementHandle() throws SQLException;
/**
* Get the current statement type of this statement.
*
* The returned value is one of the {@code TYPE_*} constant values defined in {@link FirebirdPreparedStatement}, or
* {@code 0} if the statement currently does not have a statement type.
*
*
* @return The identifier for the given statement's type
*/
public abstract int getStatementType();
/**
* {@inheritDoc}
*
* Subclasses overriding this method are expected to call this method with {@code super.close()} at an appropriate
* point to mark it closed.
*
*/
@Override
public void close() throws SQLException {
warning = null;
closed = true;
}
@Override
public final boolean isClosed() {
return closed;
}
@Override
public boolean isValid() {
return !closed;
}
/**
* Check if this statement is valid (not closed). This method should be invoked before executing any action which
* requires a valid/open statement.
*
* @throws SQLException
* if this Statement has been closed and cannot be used anymore.
*/
protected final void checkValidity() throws SQLException {
if (closed) {
throw new SQLNonTransientException("Statement is already closed", SQL_STATE_INVALID_STATEMENT_ID);
}
}
/**
* Completes this statement with {@link CompletionReason#OTHER}.
*
* @throws SQLException
* for failures completing this statement
* @see #completeStatement(CompletionReason)
*/
public final void completeStatement() throws SQLException {
completeStatement(CompletionReason.OTHER);
}
/**
* Completes this statement with {@code reason}.
*
* On completion, any open result set will be closed, and possibly the statement itself may be closed.
*
*
* @param reason
* completion reason
* @throws SQLException
* for failures completing this statement
*/
public abstract void completeStatement(CompletionReason reason) throws SQLException;
@Override
public final boolean isPoolable() throws SQLException {
try (var ignored = withLock()) {
checkValidity();
return poolable;
}
}
@Override
public final void setPoolable(boolean poolable) throws SQLException {
try (var ignored = withLock()) {
checkValidity();
this.poolable = poolable;
}
}
@Override
public final boolean isCloseOnCompletion() throws SQLException {
try (var ignored = withLock()) {
checkValidity();
return closeOnCompletion;
}
}
@Override
public final void closeOnCompletion() throws SQLException {
try (var ignored = withLock()) {
checkValidity();
closeOnCompletion = true;
}
}
/**
* Closes this statement if {@code closeOnCompletion} is set to {@code true}, does nothing if set to {@code false}.
*
* @throws SQLException
* for errors closing this statement
* @see #closeOnCompletion()
* @see #close()
*/
protected final void performCloseOnCompletion() throws SQLException {
if (closeOnCompletion) {
close();
}
}
/**
* @return current fetch config for this statement
* @since 6
*/
protected final FetchConfig fetchConfig() {
try (var ignored = withLock()) {
return fetchConfig;
}
}
/**
* @return result set behavior for this statement
* @since 6
*/
protected final ResultSetBehavior resultSetBehavior() {
return fetchConfig().resultSetBehavior();
}
@SuppressWarnings("MagicConstant")
@Override
public final int getResultSetType() throws SQLException {
checkValidity();
return resultSetBehavior().type();
}
@SuppressWarnings("MagicConstant")
@Override
public final int getResultSetConcurrency() throws SQLException {
checkValidity();
return resultSetBehavior().concurrency();
}
@Override
public final int getResultSetHoldability() throws SQLException {
checkValidity();
return resultSetBehavior().holdability();
}
@Override
public final int getMaxRows() throws SQLException {
checkValidity();
return fetchConfig().maxRows();
}
@Override
public final void setMaxRows(int max) throws SQLException {
checkValidity();
try (var ignored = withLock()) {
fetchConfig = fetchConfig.withMaxRows(max);
}
}
/**
* {@inheritDoc}
*
* Jaybird does not support maxRows exceeding {@link Integer#MAX_VALUE}, if a larger value is set, Jaybird will
* add a warning to the statement and reset the maximum to 0.
*
*/
@Override
public final void setLargeMaxRows(long max) throws SQLException {
if (max > Integer.MAX_VALUE) {
addWarning(new SQLWarning(
"Implementation limit: maxRows cannot exceed Integer.MAX_VALUE, value was %d, reset to 0"
.formatted(max), SQL_STATE_INVALID_ATTR_VALUE));
max = 0;
}
setMaxRows((int) max);
}
/**
* {@inheritDoc}
*
* Jaybird does not support maxRows exceeding {@link Integer#MAX_VALUE}, the return value of this method is the
* same as {@link #getMaxRows()}.
*
*/
@Override
public final long getLargeMaxRows() throws SQLException {
return getMaxRows();
}
@Override
public final int getFetchSize() throws SQLException {
checkValidity();
return fetchConfig().fetchSize();
}
@Override
public final void setFetchSize(int rows) throws SQLException {
checkValidity();
try (var ignored = withLock()) {
fetchConfig = fetchConfig.withFetchSize(rows);
}
}
@SuppressWarnings("MagicConstant")
@Override
public final int getFetchDirection() throws SQLException {
checkValidity();
return fetchConfig().direction();
}
@Override
public final void setFetchDirection(int direction) throws SQLException {
checkValidity();
try (var ignored = withLock()) {
fetchConfig = fetchConfig.withDirection(direction);
}
}
@Override
public final void setCursorName(@Nullable String cursorName) throws SQLException {
checkValidity();
try (var ignored = withLock()) {
this.cursorName = cursorName;
}
}
/**
* @return current value of {@code cursorName}
* @see #setCursorName(String)
*/
protected final @Nullable String getCursorName() {
return cursorName;
}
/**
* {@inheritDoc}
*
* If connection property {@code reportSQLWarnings} is set to {@code NONE} (case-insensitive), this method will
* not report warnings and always return {@code null}.
*
*/
@Override
public final @Nullable SQLWarning getWarnings() throws SQLException {
checkValidity();
return warning;
}
@Override
public final void clearWarnings() throws SQLException {
checkValidity();
warning = null;
}
protected final void addWarning(SQLWarning warning) {
try (var ignored = withLock()) {
if (connection.isIgnoreSQLWarnings()) return;
SQLWarning currentWarning = this.warning;
if (currentWarning == null) {
this.warning = warning;
} else {
currentWarning.setNextWarning(warning);
}
}
}
@Override
public final int getLocalStatementId() {
return localStatementId;
}
@Override
public final int hashCode() {
return localStatementId;
}
@Override
public final boolean equals(Object other) {
return other instanceof FirebirdStatement otherStmt
&& this.localStatementId == otherStmt.getLocalStatementId();
}
/**
* @see FbAttachment#withLock()
*/
protected final LockCloseable withLock() {
return connection.withLock();
}
}