All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.jdbc.FBPreparedStatement Maven / Gradle / Ivy

The newest version!
/*
 * 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.CursorFlag;
import org.firebirdsql.gds.ng.FbBatchConfig;
import org.firebirdsql.gds.ng.FbStatement;
import org.firebirdsql.gds.ng.LockCloseable;
import org.firebirdsql.gds.ng.StatementType;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.gds.ng.fields.RowValue;
import org.firebirdsql.gds.ng.listeners.StatementListener;
import org.firebirdsql.jdbc.field.BlobListenableField;
import org.firebirdsql.jdbc.field.FBField;
import org.firebirdsql.jdbc.field.FBFlushableField;
import org.firebirdsql.jdbc.field.FBFlushableField.CachedObject;
import org.firebirdsql.jdbc.field.FieldDataProvider;
import org.firebirdsql.util.InternalApi;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.sql.Date;
import java.util.*;

import static java.util.Collections.emptyList;
import static org.firebirdsql.jdbc.SQLStateConstants.SQL_STATE_GENERAL_ERROR;

/**
 * Implementation of {@link java.sql.PreparedStatement}.
 * 

* 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 PreparedStatement} and {@link FirebirdPreparedStatement} interfaces. *

* * @author David Jencks * @author Roman Rokytskyy * @author Mark Rotteveel */ @SuppressWarnings("RedundantThrows") @InternalApi @NullMarked public class FBPreparedStatement extends FBStatement implements FirebirdPreparedStatement { public static final String METHOD_NOT_SUPPORTED = "This method is only supported on Statement and not supported on PreparedStatement and CallableStatement"; private static final String UNICODE_STREAM_NOT_SUPPORTED = "Unicode stream not supported"; private static final FBField[] FIELDS_NOT_INITIALIZED = new FBField[0]; private final boolean metaDataQuery; /** * This flag is needed to guarantee the correct behavior in case when it * was created without controlling Connection object (in some metadata * queries we have only GDSHelper instance) */ private final boolean standaloneStatement; /** * This flag is needed to prevent throwing an exception for the case when * result set is returned for INSERT statement and the statement should * return the generated keys. */ private final boolean generatedKeys; private FBField[] fields = FIELDS_NOT_INITIALIZED; // we need to handle procedure execution separately, // because in this case we must send out_xsqlda to the server. private boolean isExecuteProcedureStatement; private final FBObjectListener.BlobListener blobListener; private RowValue fieldValues = RowValue.EMPTY_ROW_VALUE; private @Nullable Batch batch; /** * Create instance of this class for the specified result set type and concurrency. This constructor is used only in * {@link FBCallableStatement} since the statement is prepared right before the execution. * * @param connection * connection to be used * @param rsBehavior * result set behavior * @param statementListener * statement listener that will be notified about the statement start, close and completion * @param blobListener * blob listener that will be notified about the statement start and completion * @throws SQLException * if something went wrong. */ protected FBPreparedStatement(FBConnection connection, ResultSetBehavior rsBehavior, FBObjectListener.StatementListener statementListener, FBObjectListener.BlobListener blobListener) throws SQLException { super(connection, rsBehavior, statementListener); this.blobListener = blobListener; this.standaloneStatement = false; this.metaDataQuery = false; this.generatedKeys = false; setPoolable(true); } /** * Create instance of this class and prepare SQL statement. * * @param connection * connection to be used * @param sql * SQL statement to prepare * @param rsBehavior * result set behavior * @param statementListener * statement listener that will be notified about the statement start, close and completion * @param blobListener * blob listener that will be notified about the statement start and completion * @param metaDataQuery * {@code true} for a metadata query, {@code false} for a normal query * @param standaloneStatement * {@code true} for a standalone statement (should only be used when {@code metaDataQuery == true}) * @param generatedKeys * {@code true} if this statement produces a generated keys result set * @throws SQLException * if something went wrong. */ protected FBPreparedStatement(FBConnection connection, String sql, ResultSetBehavior rsBehavior, FBObjectListener.StatementListener statementListener, FBObjectListener.@Nullable BlobListener blobListener, boolean metaDataQuery, boolean standaloneStatement, boolean generatedKeys) throws SQLException { super(connection, rsBehavior, statementListener); this.blobListener = blobListener != null ? blobListener : FBObjectListener.NoActionBlobListener.instance(); this.metaDataQuery = metaDataQuery; this.standaloneStatement = standaloneStatement; this.generatedKeys = generatedKeys; setPoolable(true); try (LockCloseable ignored = connection.withLock()) { if (metaDataQuery) { fbStatement.clearCursorFlag(CursorFlag.CURSOR_TYPE_SCROLLABLE); } // TODO See http://tracker.firebirdsql.org/browse/JDBC-352 notifyStatementStarted(); try { prepareFixedStatement(sql); } catch (Exception e) { notifyStatementCompleted(false, e); throw e; } } } @Override public void completeStatement(CompletionReason reason) throws SQLException { if (!metaDataQuery || reason == CompletionReason.CONNECTION_ABORT) { super.completeStatement(reason); } else { notifyStatementCompleted(); } } @Override protected void notifyStatementCompleted(boolean success) throws SQLException { try { super.notifyStatementCompleted(success); } finally { if (metaDataQuery && standaloneStatement) close(); } } @Override public ResultSet executeQuery() throws SQLException { return executeQuery(false); } private ResultSet executeQuery(boolean metaDataQuery) throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); notifyStatementStarted(); try { if (!internalExecute(isExecuteProcedureStatement)) { throw queryProducedNoResultSet(); } FBResultSet rs = getResultSet(metaDataQuery); assert rs != null : "a non-null ResultSet is required at this point"; return rs; } catch (Exception e) { notifyStatementCompleted(true, e); throw e; } } } @Override public int executeUpdate() throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); notifyStatementStarted(); try { if (internalExecute(isExecuteProcedureStatement) && !isGeneratedKeyQuery()) { throw updateReturnedResultSet(); } int updateCount = getUpdateCountMinZero(); notifyStatementCompleted(); return updateCount; } catch (Exception e) { notifyStatementCompleted(true, e); throw e; } } } @Override public void close() throws SQLException { try (var ignored = withLock()) { try { Batch batch = this.batch; if (batch != null) { this.batch = null; batch.close(); } } finally { super.close(); } } } public FirebirdParameterMetaData getFirebirdParameterMetaData() throws SQLException { return new FBParameterMetaData(fbStatement.getParameterDescriptor(), connection); } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { getField(parameterIndex).setNull(); } @Override public void setBinaryStream(int parameterIndex, @Nullable InputStream inputStream, int length) throws SQLException { getField(parameterIndex).setBinaryStream(inputStream, length); } @Override public void setBinaryStream(int parameterIndex, @Nullable InputStream inputStream, long length) throws SQLException { getField(parameterIndex).setBinaryStream(inputStream, length); } @Override public void setBinaryStream(int parameterIndex, @Nullable InputStream inputStream) throws SQLException { getField(parameterIndex).setBinaryStream(inputStream); } @Override public void setBytes(int parameterIndex, byte @Nullable [] x) throws SQLException { getField(parameterIndex).setBytes(x); } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { getField(parameterIndex).setBoolean(x); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { getField(parameterIndex).setByte(x); } @Override public void setDate(int parameterIndex, @Nullable Date x) throws SQLException { getField(parameterIndex).setDate(x); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { getField(parameterIndex).setDouble(x); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { getField(parameterIndex).setFloat(x); } @Override public void setInt(int parameterIndex, int x) throws SQLException { getField(parameterIndex).setInteger(x); } @Override public void setLong(int parameterIndex, long x) throws SQLException { getField(parameterIndex).setLong(x); } @Override public void setObject(int parameterIndex, @Nullable Object x) throws SQLException { getField(parameterIndex).setObject(x); } /** * {@inheritDoc} *

* Implementation note: behaves as {@link #setObject(int, Object, int, int)} called with * {@link SQLType#getVendorTypeNumber()}. *

*/ @Override public void setObject(int parameterIndex, @Nullable Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { setObject(parameterIndex, x, targetSqlType.getVendorTypeNumber(), scaleOrLength); } /** * {@inheritDoc} *

* Implementation note: behaves as {@link #setObject(int, Object, int)} called with * {@link SQLType#getVendorTypeNumber()}. *

*/ @Override public void setObject(int parameterIndex, @Nullable Object x, SQLType targetSqlType) throws SQLException { setObject(parameterIndex, x, targetSqlType.getVendorTypeNumber()); } @Override public void setShort(int parameterIndex, short x) throws SQLException { getField(parameterIndex).setShort(x); } @Override public void setString(int parameterIndex, @Nullable String x) throws SQLException { getField(parameterIndex).setString(x); } @Override public void setTime(int parameterIndex, @Nullable Time x) throws SQLException { getField(parameterIndex).setTime(x); } @Override public void setTimestamp(int parameterIndex, @Nullable Timestamp x) throws SQLException { getField(parameterIndex).setTimestamp(x); } @Override public void setBigDecimal(int parameterIndex, @Nullable BigDecimal x) throws SQLException { getField(parameterIndex).setBigDecimal(x); } /** * Returns the {@link FieldDescriptor} of the specified parameter. * * @param columnIndex 1-based index of the parameter * @return Field descriptor */ protected FieldDescriptor getParameterDescriptor(int columnIndex) { return fbStatement.getParameterDescriptor().getFieldDescriptor(columnIndex - 1); } /** * Factory method for the field access objects */ protected FBField getField(int columnIndex) throws SQLException { checkValidity(); if (columnIndex > fields.length) { throw new SQLException("Invalid column index: " + columnIndex, SQLStateConstants.SQL_STATE_INVALID_DESC_FIELD_ID); } return fields[columnIndex - 1]; } /** * {@inheritDoc} *

* Implementation note: works identical to {@link #setBinaryStream(int, InputStream, int)}. *

*/ @Override public final void setAsciiStream(int parameterIndex, @Nullable InputStream x, int length) throws SQLException { setBinaryStream(parameterIndex, x, length); } /** * {@inheritDoc} *

* Implementation note: works identical to {@link #setBinaryStream(int, InputStream, long)}. *

*/ @Override public final void setAsciiStream(int parameterIndex, @Nullable InputStream x, long length) throws SQLException { setBinaryStream(parameterIndex, x, length); } /** * {@inheritDoc} *

* Implementation note: works identical to {@link #setBinaryStream(int, InputStream)}. *

*/ @Override public final void setAsciiStream(int parameterIndex, @Nullable InputStream x) throws SQLException { setBinaryStream(parameterIndex, x); } /** * Method is no longer supported since Jaybird 3.0. *

* For old behavior use {@link #setBinaryStream(int, InputStream, int)}. For JDBC suggested behavior, * use {@link #setCharacterStream(int, Reader, int)}. *

* * @throws SQLFeatureNotSupportedException Always * @deprecated */ @Deprecated(since = "1") public void setUnicodeStream(int parameterIndex, @Nullable InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException(UNICODE_STREAM_NOT_SUPPORTED); } /** * {@inheritDoc} *

* Jaybird does not support array types. *

*/ @Override public void setURL(int parameterIndex, @Nullable URL url) throws SQLException { throw new FBDriverNotCapableException("Type URL not supported"); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setCharacterStream(int, Reader, long)}. *

*/ @Override public void setNCharacterStream(int parameterIndex, @Nullable Reader value, long length) throws SQLException { setCharacterStream(parameterIndex, value, length); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setCharacterStream(int, Reader)}. *

*/ @Override public void setNCharacterStream(int parameterIndex, @Nullable Reader value) throws SQLException { setCharacterStream(parameterIndex, value); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setClob(int, Reader, long)}. *

*/ @Override public void setNClob(int parameterIndex, @Nullable Reader reader, long length) throws SQLException { setClob(parameterIndex, reader, length); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setClob(int, Reader)}. *

*/ @Override public void setNClob(int parameterIndex, @Nullable Reader reader) throws SQLException { setClob(parameterIndex, reader); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setString(int, String)}. *

*/ @Override public void setNString(int parameterIndex, @Nullable String value) throws SQLException { setString(parameterIndex, value); } @Override public void clearParameters() throws SQLException { checkValidity(); fieldValues.reset(); } /** * {@inheritDoc} *

* Implementation note: ignores {@code scaleOrLength} and {@code targetSqlType} and works as * {@link #setObject(int, Object)}, {@code scaleOrLength} is not ignored if {@code x} is a {@link Reader} or * {@link InputStream}. *

*/ @Override public void setObject(int parameterIndex, @Nullable Object x, int targetSqlType, int scaleOrLength) throws SQLException { getField(parameterIndex).setObject(x, scaleOrLength); } /** * {@inheritDoc} *

* Implementation note: ignores {@code targetSqlType} and works as {@link #setObject(int, Object)}. *

*/ @Override public void setObject(int parameterIndex, @Nullable Object x, int targetSqlType) throws SQLException { setObject(parameterIndex, x); } @Override public boolean execute() throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); notifyStatementStarted(); try { boolean hasResultSet = internalExecute(isExecuteProcedureStatement); if (!hasResultSet) { notifyStatementCompleted(); } return hasResultSet; } catch (Exception e) { notifyStatementCompleted(true, e); throw e; } } } /** * Execute metadata query. This method is similar to {@link #executeQuery()} however, it always returns * a cached result set and strings in the result set are always trimmed (server defines system tables using CHAR * data type, but it should be used as VARCHAR). * * @return result set corresponding to the specified query. * @throws SQLException * if something went wrong or no result set was available. */ ResultSet executeMetaDataQuery() throws SQLException { return executeQuery(true); } /** * Execute this statement. Method checks whether all parameters are set, * flushes all "flushable" fields that might contain cached data and * executes the statement. * * @param sendOutParams Determines if the XSQLDA structure should be sent to the * database * @return {@code true} if the statement has more result sets. */ protected boolean internalExecute(boolean sendOutParams) throws SQLException { checkAllParametersSet(); try (LockCloseable ignored = withLock()) { flushFields(); return internalExecute(fieldValues); } } private void checkAllParametersSet() throws SQLException { // This relies on FBFlushableField explicitly initializing the field with null when setting a cached object // This way we avoid flushing cached objects unless we are really going to execute fbStatement.validateParameters(fieldValues); } @Override protected boolean isGeneratedKeyQuery() { return generatedKeys; } /** * Flush fields that might have cached data. * * @throws SQLException if something went wrong. */ @SuppressWarnings("ForLoopReplaceableByForEach") private void flushFields() throws SQLException { // flush any cached data that can be hanging for (int i = 0; i < fields.length; i++) { if (fields[i] instanceof FBFlushableField flushableField) { flushableField.flushCachedData(); } } } private Batch requireBatch() throws SQLException { Batch batch = this.batch; if (batch != null) { return batch; } return this.batch = createBatch(); } // NOTE: This is not used for FBCallableStatement! private Batch createBatch() throws SQLException { if (canExecuteUsingServerBatch()) { return new ServerBatch( FbBatchConfig.of(FbBatchConfig.HALT_AT_FIRST_ERROR, FbBatchConfig.UPDATE_COUNTS, FbBatchConfig.SERVER_DEFAULT_DETAILED_ERRORS, connection.getServerBatchBufferSize()), fbStatement); } else { return new EmulatedPreparedStatementBatch(); } } private boolean canExecuteUsingServerBatch() { // enabled for connection return connection.isUseServerBatch() // supported by statement implementation && fbStatement.supportBatchUpdates() // server batch execution throws isc_batch_param when executing a statement without parameters && fbStatement.getParameterDescriptor().getCount() != 0 // server batch execution cannot produce rows from RETURNING && !isGeneratedKeyQuery(); } @Override public void addBatch() throws SQLException { checkValidity(); checkAllParametersSet(); try (LockCloseable ignored = withLock()) { final BatchedRowValue batchedValues = new BatchedRowValue(fieldValues.deepCopy()); for (int i = 0; i < batchedValues.getCount(); i++) { if (getField(i + 1) instanceof FBFlushableField flushableField) { batchedValues.setCachedObject(i, flushableField.getCachedObject()); } } requireBatch().addBatch(batchedValues); } } @Override public void clearBatch() throws SQLException { checkValidity(); try (LockCloseable ignored = withLock()) { Batch batch = this.batch; if (batch != null) { // TODO Find open streams and close them? batch.clearBatch(); } } } @Override protected List executeBatchInternal() throws SQLException { try (LockCloseable ignored = withLock()) { checkValidity(); notifyStatementStarted(); try { List results = requireBatch().execute(); notifyStatementCompleted(); return results; } catch (Exception e) { notifyStatementCompleted(false, e); throw e; } } } @Override public void setCharacterStream(int parameterIndex, @Nullable Reader reader, int length) throws SQLException { getField(parameterIndex).setCharacterStream(reader, length); } @Override public void setCharacterStream(int parameterIndex, @Nullable Reader reader, long length) throws SQLException { getField(parameterIndex).setCharacterStream(reader, length); } @Override public void setCharacterStream(int parameterIndex, @Nullable Reader reader) throws SQLException { getField(parameterIndex).setCharacterStream(reader); } /** * {@inheritDoc} *

* Jaybird does not support ref types. *

*/ @Override public void setRef(int i, @Nullable Ref x) throws SQLException { throw new FBDriverNotCapableException("Type REF not supported"); } @Override public void setBlob(int parameterIndex, @Nullable Blob blob) throws SQLException { getField(parameterIndex).setBlob(blob); } @Override public void setBlob(int parameterIndex, @Nullable InputStream inputStream, long length) throws SQLException { setBinaryStream(parameterIndex, inputStream, length); } @Override public void setBlob(int parameterIndex, @Nullable InputStream inputStream) throws SQLException { setBinaryStream(parameterIndex, inputStream); } @Override public void setClob(int parameterIndex, @Nullable Clob clob) throws SQLException { getField(parameterIndex).setClob(clob); } @Override public void setClob(int parameterIndex, @Nullable Reader reader, long length) throws SQLException { setCharacterStream(parameterIndex, reader, length); } @Override public void setClob(int parameterIndex, @Nullable Reader reader) throws SQLException { setCharacterStream(parameterIndex, reader); } /** * {@inheritDoc} *

* Jaybird does not support array types. *

*/ @Override public void setArray(int i, @Nullable Array x) throws SQLException { throw new FBDriverNotCapableException("Type ARRAY not yet supported"); } @Override public @Nullable ResultSetMetaData getMetaData() throws SQLException { checkValidity(); // TODO Return null for statements without result set? return new FBResultSetMetaData(fbStatement.getRowDescriptor(), connection); } @Override public void setDate(int parameterIndex, @Nullable Date x, @Nullable Calendar cal) throws SQLException { getField(parameterIndex).setDate(x, cal); } @Override public void setTime(int parameterIndex, @Nullable Time x, @Nullable Calendar cal) throws SQLException { getField(parameterIndex).setTime(x, cal); } @Override public void setTimestamp(int parameterIndex, @Nullable Timestamp x, @Nullable Calendar cal) throws SQLException { getField(parameterIndex).setTimestamp(x, cal); } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { // all nulls are represented the same irrespective of type setNull(parameterIndex, sqlType); } /** * Prepare fixed statement and initialize parameters. */ @Override protected void prepareFixedStatement(String sql) throws SQLException { super.prepareFixedStatement(sql); RowDescriptor rowDescriptor = fbStatement.getParameterDescriptor(); assert rowDescriptor != null : "RowDescriptor should not be null after prepare"; int fieldCount = rowDescriptor.getCount(); fieldValues = rowDescriptor.createDefaultFieldValues(); fields = new FBField[fieldCount]; for (int i = 0; i < fieldCount; i++) { final int fieldPosition = i; FieldDataProvider dataProvider = new FieldDataProvider() { public byte @Nullable [] getFieldData() { return fieldValues.getFieldData(fieldPosition); } public void setFieldData(byte @Nullable [] data) { fieldValues.setFieldData(fieldPosition, data); } }; // FIXME check if we can safely pass cached here FBField field = FBField.createField(getParameterDescriptor(i + 1), dataProvider, gdsHelper, false); if (field instanceof BlobListenableField blobListenableField) { blobListenableField.setBlobListener(blobListener); } fields[i] = field; } this.isExecuteProcedureStatement = fbStatement.getType() == StatementType.STORED_PROCEDURE; } @Override public ParameterMetaData getParameterMetaData() throws SQLException { return getFirebirdParameterMetaData(); } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #setClob(int, Clob)}. *

*/ @Override public void setNClob(int parameterIndex, @Nullable NClob value) throws SQLException { setClob(parameterIndex, value); } @Override public void setRowId(int parameterIndex, @Nullable RowId x) throws SQLException { getField(parameterIndex).setRowId(x); } /** * {@inheritDoc} *

* Jaybird does not support SQLXML. *

*/ @Override public void setSQLXML(int parameterIndex, @Nullable SQLXML xmlObject) throws SQLException { throw new FBDriverNotCapableException("Type SQLXML not supported"); } // Methods not allowed to be used on PreparedStatement and CallableStatement @Override public ResultSet executeQuery(String sql) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public int executeUpdate(String sql) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public boolean execute(String sql) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public void addBatch(String sql) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public int executeUpdate(String sql, int[] columnIndex) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public boolean execute(String sql, String[] columnNames) throws SQLException { throw new SQLNonTransientException(METHOD_NOT_SUPPORTED, SQL_STATE_GENERAL_ERROR); } @Override public long executeLargeUpdate() throws SQLException { executeUpdate(); return getLargeUpdateCountMinZero(); } /** * This method is for internal implementation use only. * * @return {@code true} if this prepared statement was initialized (i.e.: prepared). */ boolean isInitialized() { return fields != FIELDS_NOT_INITIALIZED; } private static final class BatchStatementListener implements StatementListener { private final List rows; private BatchStatementListener(int expectedSize) { rows = new ArrayList<>(expectedSize); } @Override public void receivedRow(FbStatement sender, RowValue rowValue) { rows.add(rowValue); } public List getRows() { return new ArrayList<>(rows); } } private final class BatchedRowValue implements Batch.BatchRowValue { private final RowValue rowValue; private @Nullable Object @Nullable [] cachedObjects; private BatchedRowValue(RowValue rowValue) { this.rowValue = rowValue; } private int getCount() { return rowValue.getCount(); } private void setCachedObject(int index, @Nullable Object object) { checkBounds(index); if (cachedObjects == null) { cachedObjects = new Object[getCount()]; } cachedObjects[index] = object; } private @Nullable Object getCachedObject(int index) { checkBounds(index); if (cachedObjects == null) { return null; } return cachedObjects[index]; } private void checkBounds(int index) { if (index < 0 || index >= getCount()) { throw new ArrayIndexOutOfBoundsException(index); } } @Override public RowValue toRowValue() throws SQLException { // NOTE This is basically the old implementation of flushing fields. The use of the fieldValues field is // a bit of a kludge, but we use fields for the operation, which are hardwired to it. // We may want to see if we can move the flushing down into CachedObject or something like that RowValue preservedFieldValues = fieldValues; try { fieldValues = rowValue; for (int i = 0; i < fieldValues.getCount(); i++) { if (getField(i + 1) instanceof FBFlushableField flushableField) { flushableField.setCachedObject((CachedObject) getCachedObject(i)); } } flushFields(); return rowValue; } finally { fieldValues = preservedFieldValues; } } } /** * Emulated batch, which executes row values individually. *

* This implementation is never really closed, and, once instantiated, its lifetime is tied to the parent statement. * It does not check whether the statement or batch is closed, instead relying on the caller to check if the * statement is closed or not. *

*/ private final class EmulatedPreparedStatementBatch implements Batch { private final Deque batchRowValues = new ArrayDeque<>(); @Override public void addBatch(BatchRowValue rowValue) throws SQLException { batchRowValues.addLast(rowValue); } private boolean isEmpty() { return batchRowValues.isEmpty(); } @Override public List execute() throws SQLException { if (isEmpty()) { return emptyList(); } final int size = batchRowValues.size(); final BatchStatementListener batchStatementListener; if (isGeneratedKeyQuery()) { batchStatementListener = new BatchStatementListener(size); fbStatement.addStatementListener(batchStatementListener); } else { batchStatementListener = null; } final List results = new ArrayList<>(size); try { Deque batchRowValues = this.batchRowValues; BatchRowValue batchRowValue; while ((batchRowValue = batchRowValues.pollFirst()) != null) { results.add(executeSingleForBatch(batchRowValue)); } return results; } catch (SQLException e) { throw createBatchUpdateException(e.getMessage(), e.getSQLState(), e.getErrorCode(), results, e); } finally { currentStatementResult = StatementResult.NO_MORE_RESULTS; if (batchStatementListener != null) { fbStatement.removeStatementListener(batchStatementListener); specialResult.clear(); specialResult.addAll(batchStatementListener.getRows()); } clearBatch(); } } private long executeSingleForBatch(BatchRowValue batchRowValue) throws SQLException { if (internalExecute(batchRowValue.toRowValue())) { throw batchStatementReturnedResultSet(); } return getLargeUpdateCountMinZero(); } @Override public void clearBatch() { batchRowValues.clear(); } @Override public void close() { clearBatch(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy