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

org.firebirdsql.jdbc.FBResultSet 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.JaybirdErrorCodes;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.gds.ng.FbStatement;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.gds.ng.fields.RowValue;
import org.firebirdsql.jaybird.props.PropertyConstants;
import org.firebirdsql.jaybird.util.SQLExceptionThrowingFunction;
import org.firebirdsql.jaybird.util.UncheckedSQLException;
import org.firebirdsql.jaybird.util.SQLExceptionChainBuilder;
import org.firebirdsql.jdbc.field.FBCloseableField;
import org.firebirdsql.jdbc.field.FBField;
import org.firebirdsql.jdbc.field.FieldDataProvider;
import org.firebirdsql.jdbc.field.TrimmableField;
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.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.util.Objects.requireNonNull;

/**
 * Implementation of {@link ResultSet}.
 * 

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

* * @author David Jencks * @author Roman Rokytskyy * @author Mark Rotteveel */ @SuppressWarnings("RedundantThrows") @InternalApi @NullMarked public class FBResultSet implements ResultSet, FirebirdResultSet, FBObjectListener.FetcherListener { private static final String UNICODE_STREAM_NOT_SUPPORTED = "Unicode stream not supported"; private static final String TYPE_SQLXML = "SQLXML"; private final @Nullable AbstractStatement statement; private final FBFetcher fbFetcher; private @Nullable FirebirdRowUpdater rowUpdater; protected final @Nullable FBConnection connection; protected final @Nullable GDSHelper gdsHelper; protected final RowDescriptor rowDescriptor; protected @Nullable RowValue row; private boolean wasNull; private final FBField[] fields; private final List closeableFields; private final Map colNames; private final @Nullable String cursorName; private final FBObjectListener.ResultSetListener listener; @Override public void rowChanged(FBFetcher fetcher, @Nullable RowValue newRow) throws SQLException { this.row = newRow; } /** * Creates a new {@code FBResultSet} instance. */ @SuppressWarnings("java:S1141") public FBResultSet(AbstractStatement statement, FBObjectListener.@Nullable ResultSetListener listener, boolean metaDataQuery) throws SQLException { this.statement = requireNonNull(statement, "statement"); FbStatement stmt = requireNonNull(statement.getStatementHandle(), "statement.statementHandle"); try { connection = requireNonNull(statement.getConnection(), "statement.connection"); gdsHelper = connection.getGDSHelper(); cursorName = statement.getCursorName(); this.listener = listener != null ? listener : FBObjectListener.NoActionResultSetListener.instance(); rowDescriptor = stmt.getRowDescriptor(); FetchConfig fetchConfig = statement.fetchConfig(); ResultSetBehavior behavior = fetchConfig.resultSetBehavior(); boolean serverSideScrollable = behavior.isScrollable() && behavior.isCloseCursorsAtCommit() && !metaDataQuery && connection.isScrollableCursor(PropertyConstants.SCROLLABLE_CURSOR_SERVER) && stmt.supportsFetchScroll(); boolean cached = metaDataQuery || behavior.isScrollable() && !serverSideScrollable || behavior.isHoldCursorsOverCommit(); fields = createFields(cached, metaDataQuery); closeableFields = toCloseableFields(fields); colNames = new HashMap<>(rowDescriptor.getCount(), 1); FBFetcher fbFetcher; if (cached) { fbFetcher = new FBCachedFetcher(gdsHelper, fetchConfig, stmt, this); if (behavior.isForwardOnly()) { fbFetcher = new ForwardOnlyFetcherDecorator(fbFetcher); } } else if (serverSideScrollable) { fbFetcher = new FBServerScrollFetcher(fetchConfig, stmt, this); } else if (statement.getCursorName() != null) { fbFetcher = new FBUpdatableCursorFetcher(gdsHelper, fetchConfig, stmt, this); } else { fbFetcher = new FBStatementFetcher(gdsHelper, fetchConfig, stmt, this); } if (behavior.isUpdatable()) { try { rowUpdater = new FBRowUpdater(connection, rowDescriptor, cached, listener); if (behavior.isScrollable()) { fbFetcher = new FBUpdatableFetcher(fbFetcher, this, rowDescriptor.createDeletedRowMarker()); } } catch (FBResultSetNotUpdatableException ex) { statement.addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_concurrencyResetReadOnlyReasonNotUpdatable) .toSQLException(SQLWarning.class)); fbFetcher.setReadOnly(); } } this.fbFetcher = fbFetcher; } catch (SQLException e) { try { // Ensure cursor is closed to avoid problems with statement reuse stmt.closeCursor(); } catch (SQLException e2) { e.addSuppressed(e2); } throw e; } } /** * Creates a FBResultSet with the columns specified by {@code rowDescriptor} and the data in {@code rows}. *

* This constructor is intended for metadata result sets, but can be used for other purposes as well. *

*

* Current implementation will ensure that strings will be trimmed on retrieval. *

* * @param rowDescriptor * column definition * @param rows * row data */ public FBResultSet(RowDescriptor rowDescriptor, List rows) throws SQLException { this(rowDescriptor, null, rows, null, false); } /** * Creates a FBResultSet with the columns specified by {@code rowDescriptor} and the data in {@code rows}. *

* Current implementation will ensure that strings will be trimmed on retrieval. *

* * @param rowDescriptor * column definition * @param connection * connection (cannot be {@code null} when {@code retrieveBlobs} is {@code true} * @param rows * row data * @param listener * result set listener * @param retrieveBlobs * {@code true} retrieves the blob data * @since 5.0.1 */ public FBResultSet(RowDescriptor rowDescriptor, @Nullable FBConnection connection, List rows, FBObjectListener.@Nullable ResultSetListener listener, boolean retrieveBlobs) throws SQLException { // TODO Evaluate if we need to share more implementation with constructor above this.connection = connection; gdsHelper = connection != null ? connection.getGDSHelper() : null; statement = null; this.listener = listener != null ? listener : FBObjectListener.NoActionResultSetListener.instance(); cursorName = null; // TODO Set specific result set types (see also previous todo) var fetchConfig = new FetchConfig(ResultSetBehavior.of()); fbFetcher = new FBCachedFetcher(rows, fetchConfig, this, rowDescriptor, gdsHelper, retrieveBlobs); this.rowDescriptor = rowDescriptor; fields = createFields(true, false); closeableFields = toCloseableFields(fields); colNames = new HashMap<>(rowDescriptor.getCount(), 1); } private FBField[] createFields(boolean cached, boolean trimStrings) throws SQLException { int fieldCount = rowDescriptor.getCount(); var fields = new FBField[fieldCount]; for (int i = 0; i < fieldCount; i++) { fields[i] = FBField.createField(rowDescriptor.getFieldDescriptor(i), new DataProvider(i), gdsHelper, cached); if (trimStrings && fields[i] instanceof TrimmableField trimmableField) { trimmableField.setTrimTrailing(true); } } return fields; } private static List toCloseableFields(FBField[] fields) { return Arrays.stream(fields) .filter(FBCloseableField.class::isInstance) .map(FBCloseableField.class::cast) .toList(); } /** * Notify the row updater about the new row that was fetched. This method * must be called after each change in cursor position. */ private void notifyRowUpdater() throws SQLException { if (rowUpdater != null) { rowUpdater.setRow(row); } } /** * Check if statement is open and prepare statement for cursor move. * * @throws SQLException * if statement is closed. */ protected void checkCursorMove() throws SQLException { checkOpen(); closeFields(); } /** * Check if ResultSet is open. * * @throws SQLException * if ResultSet is closed. */ protected void checkOpen() throws SQLException { if (isClosed()) { throw new SQLException("The result set is closed", SQLStateConstants.SQL_STATE_INVALID_CURSOR_STATE); } } /** * Checks if the result set is scrollable * * @throws SQLException * if ResultSet is not scrollable */ protected void checkScrollable() throws SQLException { if (behavior().isForwardOnly()) { throw FbExceptionBuilder.forNonTransientException(JaybirdErrorCodes.jb_operationNotAllowedOnForwardOnly) .toSQLException(); } } /** * Close the fields if they were open (applies mainly to the stream fields). * * @throws SQLException * if something wrong happened. */ protected void closeFields() throws SQLException { // TODO See if we can apply completion reason logic (e.g. no need to close blob on commit) wasNull = false; // if there are no fields to close, then nothing else to do if (closeableFields.isEmpty()) return; var chain = new SQLExceptionChainBuilder(); // close current fields, so that resources are freed. for (final FBCloseableField field : closeableFields) { try { field.close(); } catch (SQLException ex) { chain.append(ex); } } chain.throwIfPresent(); } @Override public boolean next() throws SQLException { checkCursorMove(); boolean result = fbFetcher.next(); if (result) notifyRowUpdater(); return result; } @Override public void close() throws SQLException { close(true, CompletionReason.OTHER); } @Override public boolean isClosed() throws SQLException { return fbFetcher.isClosed(); } void close(boolean notifyListener, CompletionReason completionReason) throws SQLException { if (isClosed()) return; var chain = new SQLExceptionChainBuilder(); try { closeFields(); } catch (SQLException ex) { chain.append(ex); } finally { try { try { fbFetcher.close(completionReason); } catch (SQLException ex) { chain.append(ex); } if (rowUpdater != null) { try { rowUpdater.close(); } catch (SQLException ex) { chain.append(ex); } } if (notifyListener) { try { listener.resultSetClosed(this); } catch (SQLException ex) { chain.append(ex); } } } finally { rowUpdater = null; } } chain.throwIfPresent(); } @Override public boolean wasNull() throws SQLException { checkOpen(); return wasNull; } /** * {@inheritDoc} *

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

*/ @Override public final @Nullable InputStream getAsciiStream(int columnIndex) throws SQLException { return getBinaryStream(columnIndex); } @Override public @Nullable BigDecimal getBigDecimal(int columnIndex) throws SQLException { return getField(columnIndex).getBigDecimal(); } @Override public @Nullable InputStream getBinaryStream(int columnIndex) throws SQLException { return getField(columnIndex).getBinaryStream(); } @Override public @Nullable Blob getBlob(int columnIndex) throws SQLException { return getField(columnIndex).getBlob(); } @Override public boolean getBoolean(int columnIndex) throws SQLException { return getField(columnIndex).getBoolean(); } @Override public byte getByte(int columnIndex) throws SQLException { return getField(columnIndex).getByte(); } @Override public byte @Nullable [] getBytes(int columnIndex) throws SQLException { return getField(columnIndex).getBytes(); } @Override public @Nullable Date getDate(int columnIndex) throws SQLException { return getField(columnIndex).getDate(); } @Override public double getDouble(int columnIndex) throws SQLException { return getField(columnIndex).getDouble(); } @Override public float getFloat(int columnIndex) throws SQLException { return getField(columnIndex).getFloat(); } @Override public int getInt(int columnIndex) throws SQLException { return getField(columnIndex).getInt(); } @Override public long getLong(int columnIndex) throws SQLException { return getField(columnIndex).getLong(); } @Override public @Nullable Object getObject(int columnIndex) throws SQLException { return getField(columnIndex).getObject(); } @Override public short getShort(int columnIndex) throws SQLException { return getField(columnIndex).getShort(); } @Override public @Nullable String getString(int columnIndex) throws SQLException { return getField(columnIndex).getString(); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable String getNString(int columnIndex) throws SQLException { return getString(columnIndex); } @Override public @Nullable Time getTime(int columnIndex) throws SQLException { return getField(columnIndex).getTime(); } @Override public @Nullable Timestamp getTimestamp(int columnIndex) throws SQLException { return getField(columnIndex).getTimestamp(); } /** * Method is no longer supported since Jaybird 3.0. *

* For old behavior use {@link #getBinaryStream(int)}. For JDBC suggested behavior, * use {@link #getCharacterStream(int)}. *

* * @throws SQLFeatureNotSupportedException * Always * @deprecated */ @Deprecated(since = "1") public @Nullable InputStream getUnicodeStream(int columnIndex) throws SQLException { throw new SQLFeatureNotSupportedException(UNICODE_STREAM_NOT_SUPPORTED); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable Reader getNCharacterStream(int columnIndex) throws SQLException { return getCharacterStream(columnIndex); } /** * Get the {@code FBField} object at the given column index * * @param columnIndex * The index of the parameter, 1 is the first index * @throws SQLException * If there is an error accessing the field */ public FBField getField(int columnIndex) throws SQLException { FBField field = getField(columnIndex, true); wasNull = field.isNull(); return field; } /** * Factory method for the field access objects */ public FBField getField(int columnIndex, boolean checkRowPosition) throws SQLException { checkOpen(); if (checkRowPosition && row == null && rowUpdater == null) { throw new SQLException("The result set is not in a row, use next", SQLStateConstants.SQL_STATE_NO_ROW_AVAIL); } if (columnIndex > rowDescriptor.getCount()) { throw new SQLException("Invalid column index: " + columnIndex, SQLStateConstants.SQL_STATE_INVALID_DESC_FIELD_ID); } return rowUpdater != null ? rowUpdater.getField(columnIndex - 1) : fields[columnIndex - 1]; } /** * Get a {@code FBField} by name. * * @param columnName * The name of the field to be retrieved * @throws SQLException * if the field cannot be retrieved */ public FBField getField(String columnName) throws SQLException { try { int fieldNum = colNames.computeIfAbsent(columnName, SQLExceptionThrowingFunction.toFunction(this::findColumn)); return getField(fieldNum); } catch (UncheckedSQLException e) { throw e.getCause(); } } /** * {@inheritDoc} *

* Implementation note: ignores {@code scale} and behaves identical to {@link #getBigDecimal(int)}. *

*/ @Deprecated(since = "1") @Override public @Nullable BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { return getField(columnIndex).getBigDecimal(scale); } @Override public @Nullable String getString(String columnName) throws SQLException { return getField(columnName).getString(); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable String getNString(String columnLabel) throws SQLException { return getString(columnLabel); } @Override public boolean getBoolean(String columnName) throws SQLException { return getField(columnName).getBoolean(); } @Override public byte getByte(String columnName) throws SQLException { return getField(columnName).getByte(); } @Override public short getShort(String columnName) throws SQLException { return getField(columnName).getShort(); } @Override public int getInt(String columnName) throws SQLException { return getField(columnName).getInt(); } @Override public long getLong(String columnName) throws SQLException { return getField(columnName).getLong(); } @Override public float getFloat(String columnName) throws SQLException { return getField(columnName).getFloat(); } @Override public double getDouble(String columnName) throws SQLException { return getField(columnName).getDouble(); } /** * {@inheritDoc} *

* Implementation note: ignores {@code scale} and behaves identical to {@link #getBigDecimal(String)}. *

*/ @Deprecated(since = "1") @Override public @Nullable BigDecimal getBigDecimal(String columnName, int scale) throws SQLException { return getField(columnName).getBigDecimal(scale); } @Override public byte @Nullable [] getBytes(String columnName) throws SQLException { return getField(columnName).getBytes(); } @Override public @Nullable Date getDate(String columnName) throws SQLException { return getField(columnName).getDate(); } @Override public @Nullable Time getTime(String columnName) throws SQLException { return getField(columnName).getTime(); } @Override public @Nullable Timestamp getTimestamp(String columnName) throws SQLException { return getField(columnName).getTimestamp(); } /** * {@inheritDoc} *

* Implementation note: works identical to {@link #getBinaryStream(String)}. *

*/ @Override public final @Nullable InputStream getAsciiStream(String columnName) throws SQLException { return getBinaryStream(columnName); } /** * Method is no longer supported since Jaybird 3.0. *

* For old behavior use {@link #getBinaryStream(String)}. For JDBC suggested behavior, * use {@link #getCharacterStream(String)}. *

* * @throws SQLFeatureNotSupportedException * Always * @deprecated */ @Deprecated(since = "1") @Override public @Nullable InputStream getUnicodeStream(String columnName) throws SQLException { throw new SQLFeatureNotSupportedException(UNICODE_STREAM_NOT_SUPPORTED); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable Reader getNCharacterStream(String columnLabel) throws SQLException { return getCharacterStream(columnLabel); } @Override public @Nullable InputStream getBinaryStream(String columnName) throws SQLException { return getField(columnName).getBinaryStream(); } /** * {@inheritDoc} *

* If connection property {@code reportSQLWarnings} is set to {@code NONE} (case-insensitive), this method will * not report warnings and always return {@code null}. *

*

* NOTE: The implementation currently always returns {@code null} as warnings are never recorded for result * sets. *

*/ @Override public @Nullable SQLWarning getWarnings() throws SQLException { // Warnings are never recorded return null; } @Override public void clearWarnings() throws SQLException { // nothing to do } @Override public @Nullable String getCursorName() throws SQLException { return cursorName; } @Override public ResultSetMetaData getMetaData() throws SQLException { checkOpen(); return new FBResultSetMetaData(rowDescriptor, connection); } @Override public @Nullable Object getObject(String columnName) throws SQLException { return getField(columnName).getObject(); } // See section 14.2.3 of jdbc-3.0 specification // "Column names supplied to getter methods are case-insensitive // If a select list contains the same column more than once, // the first instance of the column will be returned" @Override public int findColumn(String columnName) throws SQLException { requireNonEmpty(columnName); Predicate columnNamePredicate; if (columnName.startsWith("\"") && columnName.endsWith("\"")) { String caseSensitiveColumnName = columnName.substring(1, columnName.length() - 1); requireNonEmpty(caseSensitiveColumnName); // case-sensitively check columns columnNamePredicate = caseSensitiveColumnName::equals; } else { // case-insensitively check columns columnNamePredicate = columnName::equalsIgnoreCase; } OptionalInt position = findColumn(columnNamePredicate); if (position.isPresent()) return position.getAsInt(); if (columnNamePredicate.test("RDB$DB_KEY")) { // Fix up: RDB$DB_KEY is identified as DB_KEY in the result set OptionalInt dbKeyPosition = findColumn("DB_KEY"::equals); if (dbKeyPosition.isPresent()) return dbKeyPosition.getAsInt(); } throw new SQLException("Column name " + columnName + " not found in result set", SQLStateConstants.SQL_STATE_INVALID_DESC_FIELD_ID); } private static void requireNonEmpty(String columnName) throws SQLException { if (columnName == null || columnName.isEmpty()) { throw new SQLException("Empty string or null does not identify a column", SQLStateConstants.SQL_STATE_INVALID_DESC_FIELD_ID); } } private OptionalInt findColumn(Predicate<@Nullable String> columnNamePredicate) { // Check labels (aliases) first OptionalInt position = findColumn(columnNamePredicate, FieldDescriptor::getFieldName); if (position.isPresent()) return position; // then check underlying column names return findColumn(columnNamePredicate, FieldDescriptor::getOriginalName); } private OptionalInt findColumn(Predicate<@Nullable String> columnNamePredicate, Function columnNameAccessor) { for (int i = 0; i < rowDescriptor.getCount(); i++) { if (columnNamePredicate.test(columnNameAccessor.apply(rowDescriptor.getFieldDescriptor(i)))) { return OptionalInt.of(i + 1); } } return OptionalInt.empty(); } @Override public @Nullable Reader getCharacterStream(int columnIndex) throws SQLException { return getField(columnIndex).getCharacterStream(); } @Override public @Nullable Reader getCharacterStream(String columnName) throws SQLException { return getField(columnName).getCharacterStream(); } @Override public @Nullable BigDecimal getBigDecimal(String columnName) throws SQLException { return getField(columnName).getBigDecimal(); } @Override public boolean isBeforeFirst() throws SQLException { checkOpen(); return !fbFetcher.isEmpty() && fbFetcher.isBeforeFirst(); } @Override public boolean isAfterLast() throws SQLException { checkOpen(); return !fbFetcher.isEmpty() && fbFetcher.isAfterLast(); } @Override public boolean isFirst() throws SQLException { checkOpen(); return fbFetcher.isFirst(); } @Override public boolean isLast() throws SQLException { checkOpen(); return fbFetcher.isLast(); } @Override public void beforeFirst() throws SQLException { checkCursorMove(); fbFetcher.beforeFirst(); notifyRowUpdater(); } @Override public void afterLast() throws SQLException { checkCursorMove(); fbFetcher.afterLast(); notifyRowUpdater(); } @Override public boolean first() throws SQLException { checkCursorMove(); boolean result = fbFetcher.first(); if (result) notifyRowUpdater(); return result; } @Override public boolean last() throws SQLException { checkCursorMove(); boolean result = fbFetcher.last(); if (result) notifyRowUpdater(); return result; } @Override public int getRow() throws SQLException { checkOpen(); return fbFetcher.getRowNum(); } @Override public boolean absolute(int row) throws SQLException { checkCursorMove(); boolean result = fbFetcher.absolute(row); if (result) notifyRowUpdater(); return result; } @Override public boolean relative(int rows) throws SQLException { checkCursorMove(); boolean result = fbFetcher.relative(rows); if (result) notifyRowUpdater(); return result; } @Override public boolean previous() throws SQLException { checkCursorMove(); boolean result = fbFetcher.previous(); if (result) notifyRowUpdater(); return result; } private ResultSetBehavior behavior() { return fbFetcher.getFetchConfig().resultSetBehavior(); } @Override public void setFetchDirection(int direction) throws SQLException { if (direction == ResultSet.FETCH_REVERSE || direction == ResultSet.FETCH_UNKNOWN) { checkScrollable(); } fbFetcher.setFetchDirection(direction); } @SuppressWarnings("MagicConstant") @Override public int getFetchDirection() throws SQLException { return fbFetcher.getFetchDirection(); } @Override public void setFetchSize(int rows) throws SQLException { fbFetcher.setFetchSize(rows); } @Override public int getFetchSize() throws SQLException { return fbFetcher.getFetchSize(); } @SuppressWarnings("MagicConstant") @Override public int getType() throws SQLException { checkOpen(); return behavior().type(); } @SuppressWarnings("MagicConstant") @Override public int getConcurrency() throws SQLException { checkOpen(); return behavior().concurrency(); } @SuppressWarnings("MagicConstant") @Override public int getHoldability() throws SQLException { checkOpen(); return behavior().holdability(); } @Override public boolean rowUpdated() throws SQLException { checkUpdatable(); return fbFetcher.rowUpdated(); } @Override public boolean rowInserted() throws SQLException { checkUpdatable(); return fbFetcher.rowInserted(); } @Override public boolean rowDeleted() throws SQLException { checkUpdatable(); return fbFetcher.rowDeleted(); } /** * Checks if the result set is updatable, throwing {@link FBResultSetNotUpdatableException} otherwise. * * @throws FBResultSetNotUpdatableException * when this result set is not updatable * @see #requireRowUpdater() */ private void checkUpdatable() throws SQLException { checkOpen(); if (rowUpdater == null) { throw new FBResultSetNotUpdatableException(); } } /** * Checks if the result set is updatable, returning the row updater, throwing * {@link FBResultSetNotUpdatableException} otherwise. * * @return row updater * @throws FBResultSetNotUpdatableException * when this result set is not updatable * @see #checkUpdatable() */ private FirebirdRowUpdater requireRowUpdater() throws SQLException { checkOpen(); FirebirdRowUpdater rowUpdater = this.rowUpdater; if (rowUpdater == null) { throw new FBResultSetNotUpdatableException(); } return rowUpdater; } @Override public void updateNull(int columnIndex) throws SQLException { checkUpdatable(); getField(columnIndex).setNull(); } @Override public void updateBoolean(int columnIndex, boolean x) throws SQLException { checkUpdatable(); getField(columnIndex).setBoolean(x); } @Override public void updateByte(int columnIndex, byte x) throws SQLException { checkUpdatable(); getField(columnIndex).setByte(x); } @Override public void updateShort(int columnIndex, short x) throws SQLException { checkUpdatable(); getField(columnIndex).setShort(x); } @Override public void updateInt(int columnIndex, int x) throws SQLException { checkUpdatable(); getField(columnIndex).setInteger(x); } @Override public void updateLong(int columnIndex, long x) throws SQLException { checkUpdatable(); getField(columnIndex).setLong(x); } @Override public void updateFloat(int columnIndex, float x) throws SQLException { checkUpdatable(); getField(columnIndex).setFloat(x); } @Override public void updateDouble(int columnIndex, double x) throws SQLException { checkUpdatable(); getField(columnIndex).setDouble(x); } @Override public void updateBigDecimal(int columnIndex, @Nullable BigDecimal x) throws SQLException { checkUpdatable(); getField(columnIndex).setBigDecimal(x); } @Override public void updateString(int columnIndex, @Nullable String x) throws SQLException { checkUpdatable(); getField(columnIndex).setString(x); } @Override public void updateBytes(int columnIndex, byte @Nullable [] x) throws SQLException { checkUpdatable(); getField(columnIndex).setBytes(x); } @Override public void updateDate(int columnIndex, @Nullable Date x) throws SQLException { checkUpdatable(); getField(columnIndex).setDate(x); } @Override public void updateTime(int columnIndex, @Nullable Time x) throws SQLException { checkUpdatable(); getField(columnIndex).setTime(x); } @Override public void updateTimestamp(int columnIndex, @Nullable Timestamp x) throws SQLException { checkUpdatable(); getField(columnIndex).setTimestamp(x); } @Override public void updateBinaryStream(int columnIndex, @Nullable InputStream x, int length) throws SQLException { checkUpdatable(); getField(columnIndex).setBinaryStream(x, length); } @Override public void updateBinaryStream(int columnIndex, @Nullable InputStream x, long length) throws SQLException { checkUpdatable(); getField(columnIndex).setBinaryStream(x, length); } @Override public void updateBinaryStream(int columnIndex, @Nullable InputStream x) throws SQLException { checkUpdatable(); getField(columnIndex).setBinaryStream(x); } @Override public void updateBinaryStream(String columnName, @Nullable InputStream x, int length) throws SQLException { checkUpdatable(); getField(columnName).setBinaryStream(x, length); } @Override public void updateBinaryStream(String columnLabel, @Nullable InputStream x, long length) throws SQLException { checkUpdatable(); getField(columnLabel).setBinaryStream(x, length); } @Override public void updateBinaryStream(String columnLabel, @Nullable InputStream x) throws SQLException { checkUpdatable(); getField(columnLabel).setBinaryStream(x); } /** * {@inheritDoc} *

* Jaybird delegates to {@link #updateObject(int, Object)} and ignores the value of {@code scaleOrLength}, if * {@code x} is anything other than a {@link Reader} or {@link InputStream}. *

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

* Jaybird delegates to {@link #updateObject(int, Object, int)} and ignores the value of {@code targetSqlType}. *

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

* Jaybird delegates to {@link #updateObject(int, Object)} and ignores the value of {@code targetSqlType} *

*/ @Override public void updateObject(int columnIndex, @Nullable Object x, SQLType targetSqlType) throws SQLException { updateObject(columnIndex, x); } @Override public void updateNull(String columnName) throws SQLException { checkUpdatable(); getField(columnName).setNull(); } @Override public void updateBoolean(String columnName, boolean x) throws SQLException { checkUpdatable(); getField(columnName).setBoolean(x); } @Override public void updateByte(String columnName, byte x) throws SQLException { checkUpdatable(); getField(columnName).setByte(x); } @Override public void updateShort(String columnName, short x) throws SQLException { checkUpdatable(); getField(columnName).setShort(x); } @Override public void updateInt(String columnName, int x) throws SQLException { checkUpdatable(); getField(columnName).setInteger(x); } @Override public void updateLong(String columnName, long x) throws SQLException { checkUpdatable(); getField(columnName).setLong(x); } @Override public void updateFloat(String columnName, float x) throws SQLException { checkUpdatable(); getField(columnName).setFloat(x); } @Override public void updateDouble(String columnName, double x) throws SQLException { checkUpdatable(); getField(columnName).setDouble(x); } @Override public void updateBigDecimal(String columnName, @Nullable BigDecimal x) throws SQLException { checkUpdatable(); getField(columnName).setBigDecimal(x); } @Override public void updateString(String columnName, @Nullable String x) throws SQLException { checkUpdatable(); getField(columnName).setString(x); } /** * {@inheritDoc} *

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

*/ @Override public void updateNString(int columnIndex, @Nullable String string) throws SQLException { updateString(columnIndex, string); } /** * {@inheritDoc} *

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

*/ @Override public void updateNString(String columnLabel, @Nullable String string) throws SQLException { updateString(columnLabel, string); } @Override public void updateBytes(String columnName, byte @Nullable [] x) throws SQLException { checkUpdatable(); getField(columnName).setBytes(x); } @Override public void updateDate(String columnName, @Nullable Date x) throws SQLException { checkUpdatable(); getField(columnName).setDate(x); } @Override public void updateTime(String columnName, @Nullable Time x) throws SQLException { checkUpdatable(); getField(columnName).setTime(x); } @Override public void updateTimestamp(String columnName, @Nullable Timestamp x) throws SQLException { checkUpdatable(); getField(columnName).setTimestamp(x); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(int columnIndex, @Nullable InputStream x, int length) throws SQLException { updateBinaryStream(columnIndex, x, length); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(String columnName, @Nullable InputStream x, int length) throws SQLException { updateBinaryStream(columnName, x, length); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(int columnIndex, @Nullable InputStream x, long length) throws SQLException { updateBinaryStream(columnIndex, x, length); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(int columnIndex, @Nullable InputStream x) throws SQLException { updateBinaryStream(columnIndex, x); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(String columnLabel, @Nullable InputStream x, long length) throws SQLException { updateBinaryStream(columnLabel, x, length); } /** * {@inheritDoc} *

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

*/ @Override public final void updateAsciiStream(String columnLabel, @Nullable InputStream x) throws SQLException { updateBinaryStream(columnLabel, x); } @Override public void updateCharacterStream(int columnIndex, @Nullable Reader x, int length) throws SQLException { checkUpdatable(); getField(columnIndex).setCharacterStream(x, length); } @Override public void updateCharacterStream(int columnIndex, @Nullable Reader x, long length) throws SQLException { checkUpdatable(); getField(columnIndex).setCharacterStream(x, length); } @Override public void updateCharacterStream(int columnIndex, @Nullable Reader x) throws SQLException { checkUpdatable(); getField(columnIndex).setCharacterStream(x); } @Override public void updateCharacterStream(String columnName, @Nullable Reader reader, int length) throws SQLException { checkUpdatable(); getField(columnName).setCharacterStream(reader, length); } @Override public void updateCharacterStream(String columnLabel, @Nullable Reader reader, long length) throws SQLException { checkUpdatable(); getField(columnLabel).setCharacterStream(reader, length); } @Override public void updateCharacterStream(String columnLabel, @Nullable Reader reader) throws SQLException { checkUpdatable(); getField(columnLabel).setCharacterStream(reader); } /** * {@inheritDoc} *

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

*/ @Override public void updateNCharacterStream(int columnIndex, @Nullable Reader x, long length) throws SQLException { updateCharacterStream(columnIndex, x, length); } /** * {@inheritDoc} *

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

*/ @Override public void updateNCharacterStream(int columnIndex, @Nullable Reader x) throws SQLException { updateCharacterStream(columnIndex, x); } /** * {@inheritDoc} *

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

*/ @Override public void updateNCharacterStream(String columnLabel, @Nullable Reader reader, long length) throws SQLException { updateCharacterStream(columnLabel, reader, length); } /** * {@inheritDoc} *

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

*/ @Override public void updateNCharacterStream(String columnLabel, @Nullable Reader reader) throws SQLException { updateCharacterStream(columnLabel, reader); } /** * {@inheritDoc} *

* Jaybird delegates to {@link #updateObject(String, Object)} and ignores the value of {@code scaleOrLength}, if * {@code x} is anything other than a {@link Reader} or {@link InputStream}. *

*/ @Override public void updateObject(String columnName, @Nullable Object x, int scaleOrLength) throws SQLException { checkUpdatable(); getField(columnName).setObject(x, scaleOrLength); } /** * {@inheritDoc} *

* Jaybird delegates to {@link #updateObject(String, Object, int)} and ignores the value of {@code targetSqlType}. *

*/ @Override public void updateObject(String columnLabel, @Nullable Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { updateObject(columnLabel, x, scaleOrLength); } @Override public void updateObject(String columnName, @Nullable Object x) throws SQLException { checkUpdatable(); getField(columnName).setObject(x); } /** * {@inheritDoc} *

* Jaybird delegates to {@link #updateObject(String, Object)} and ignores the value of {@code targetSqlType}. *

*/ @Override public void updateObject(String columnLabel, @Nullable Object x, SQLType targetSqlType) throws SQLException { updateObject(columnLabel, x); } @Override public void insertRow() throws SQLException { FirebirdRowUpdater rowUpdater = requireRowUpdater(); fbFetcher.beforeExecuteInsert(); rowUpdater.insertRow(); fbFetcher.insertRow(rowUpdater.getInsertRow()); notifyRowUpdater(); } @Override public void updateRow() throws SQLException { FirebirdRowUpdater rowUpdater = requireRowUpdater(); rowUpdater.updateRow(); fbFetcher.updateRow(rowUpdater.getNewRow()); notifyRowUpdater(); } @Override public void deleteRow() throws SQLException { requireRowUpdater().deleteRow(); fbFetcher.deleteRow(); notifyRowUpdater(); } @Override public void refreshRow() throws SQLException { FirebirdRowUpdater rowUpdater = requireRowUpdater(); rowUpdater.refreshRow(); fbFetcher.updateRow(rowUpdater.getOldRow()); // this is excessive, but we do this to keep the code uniform notifyRowUpdater(); } @Override public void cancelRowUpdates() throws SQLException { requireRowUpdater().cancelRowUpdates(); } @Override public void moveToInsertRow() throws SQLException { requireRowUpdater().moveToInsertRow(); } @Override public void moveToCurrentRow() throws SQLException { requireRowUpdater().moveToCurrentRow(); // Make sure we have the correct data of the row fbFetcher.renotifyCurrentRow(); notifyRowUpdater(); } @Override public @Nullable Statement getStatement() { return statement; } @Override public @Nullable Object getObject(int i, Map> map) throws SQLException { return getField(i).getObject(map); } @Override public @Nullable Ref getRef(int i) throws SQLException { return getField(i).getRef(); } @Override public @Nullable Clob getClob(int i) throws SQLException { return getField(i).getClob(); } @Override public @Nullable Array getArray(int i) throws SQLException { return getField(i).getArray(); } @Override public @Nullable Object getObject(String columnName, Map> map) throws SQLException { return getField(columnName).getObject(map); } @Override public @Nullable Ref getRef(String columnName) throws SQLException { return getField(columnName).getRef(); } @Override public @Nullable Blob getBlob(String columnName) throws SQLException { return getField(columnName).getBlob(); } @Override public @Nullable Clob getClob(String columnName) throws SQLException { return getField(columnName).getClob(); } @Override public @Nullable Array getArray(String columnName) throws SQLException { return getField(columnName).getArray(); } @Override public @Nullable Date getDate(int columnIndex, @Nullable Calendar cal) throws SQLException { return getField(columnIndex).getDate(cal); } @Override public @Nullable Date getDate(String columnName, @Nullable Calendar cal) throws SQLException { return getField(columnName).getDate(cal); } @Override public @Nullable Time getTime(int columnIndex, @Nullable Calendar cal) throws SQLException { return getField(columnIndex).getTime(cal); } @Override public @Nullable Time getTime(String columnName, @Nullable Calendar cal) throws SQLException { return getField(columnName).getTime(cal); } @Override public @Nullable Timestamp getTimestamp(int columnIndex, @Nullable Calendar cal) throws SQLException { return getField(columnIndex).getTimestamp(cal); } @Override public @Nullable Timestamp getTimestamp(String columnName, @Nullable Calendar cal) throws SQLException { return getField(columnName).getTimestamp(cal); } @Override public @Nullable URL getURL(int param1) throws SQLException { throw typeNotSupported("URL"); } @Override public @Nullable URL getURL(String param1) throws SQLException { throw typeNotSupported("URL"); } @Override public @Nullable T getObject(int columnIndex, Class type) throws SQLException { return getField(columnIndex).getObject(type); } @Override public @Nullable T getObject(String columnLabel, Class type) throws SQLException { return getField(columnLabel).getObject(type); } @Override public void updateRef(int param1, @Nullable Ref param2) throws SQLException { throw typeNotSupported("REF"); } @Override public void updateRef(String param1, @Nullable Ref param2) throws SQLException { throw typeNotSupported("REF"); } @Override public void updateBlob(int columnIndex, @Nullable Blob blob) throws SQLException { checkUpdatable(); getField(columnIndex).setBlob(blob); } @Override public void updateBlob(String columnLabel, @Nullable Blob blob) throws SQLException { checkUpdatable(); getField(columnLabel).setBlob(blob); } @Override public void updateBlob(int columnIndex, @Nullable InputStream inputStream, long length) throws SQLException { updateBinaryStream(columnIndex, inputStream, length); } @Override public void updateBlob(int columnIndex, @Nullable InputStream inputStream) throws SQLException { updateBinaryStream(columnIndex, inputStream); } @Override public void updateBlob(String columnLabel, @Nullable InputStream inputStream, long length) throws SQLException { updateBinaryStream(columnLabel, inputStream, length); } @Override public void updateBlob(String columnLabel, @Nullable InputStream inputStream) throws SQLException { updateBinaryStream(columnLabel, inputStream); } @Override public void updateClob(int columnIndex, @Nullable Clob clob) throws SQLException { checkUpdatable(); getField(columnIndex).setClob(clob); } @Override public void updateClob(String columnLabel, @Nullable Clob clob) throws SQLException { checkUpdatable(); getField(columnLabel).setClob(clob); } @Override public void updateClob(int columnIndex, @Nullable Reader reader, long length) throws SQLException { updateCharacterStream(columnIndex, reader, length); } @Override public void updateClob(int columnIndex, @Nullable Reader reader) throws SQLException { updateCharacterStream(columnIndex, reader); } @Override public void updateClob(String columnLabel, @Nullable Reader reader, long length) throws SQLException { updateCharacterStream(columnLabel, reader, length); } @Override public void updateClob(String columnLabel, @Nullable Reader reader) throws SQLException { updateCharacterStream(columnLabel, reader); } @Override public void updateArray(int param1, @Nullable Array param2) throws SQLException { throw new FBDriverNotCapableException("Type ARRAY not yet supported"); } @Override public void updateArray(String param1, @Nullable Array param2) throws SQLException { throw new FBDriverNotCapableException("Type ARRAY not yet supported"); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable NClob getNClob(int columnIndex) throws SQLException { return (NClob) getClob(columnIndex); } /** * {@inheritDoc} *

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

*/ @Override public @Nullable NClob getNClob(String columnLabel) throws SQLException { return (NClob) getClob(columnLabel); } @Override public @Nullable RowId getRowId(int columnIndex) throws SQLException { return getField(columnIndex).getRowId(); } @Override public @Nullable RowId getRowId(String columnLabel) throws SQLException { return getField(columnLabel).getRowId(); } @Override public @Nullable SQLXML getSQLXML(int columnIndex) throws SQLException { throw typeNotSupported(TYPE_SQLXML); } @Override public @Nullable SQLXML getSQLXML(String columnLabel) throws SQLException { throw typeNotSupported(TYPE_SQLXML); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(int columnIndex, @Nullable NClob clob) throws SQLException { updateClob(columnIndex, clob); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(int columnIndex, @Nullable Reader reader, long length) throws SQLException { updateCharacterStream(columnIndex, reader, length); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(int columnIndex, @Nullable Reader reader) throws SQLException { updateCharacterStream(columnIndex, reader); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(String columnLabel, @Nullable NClob clob) throws SQLException { updateClob(columnLabel, clob); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(String columnLabel, @Nullable Reader reader, long length) throws SQLException { updateCharacterStream(columnLabel, reader, length); } /** * {@inheritDoc} *

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

*/ @Override public void updateNClob(String columnLabel, @Nullable Reader reader) throws SQLException { updateCharacterStream(columnLabel, reader); } @Override public void updateRowId(int columnIndex, @Nullable RowId x) throws SQLException { rowIdNotUpdatable(); } @Override public void updateRowId(String columnLabel, @Nullable RowId x) throws SQLException { rowIdNotUpdatable(); } private void rowIdNotUpdatable() throws SQLException { checkUpdatable(); throw new FBDriverNotCapableException("Firebird rowId (RDB$DB_KEY) is not updatable"); } @Override public void updateSQLXML(int columnIndex, @Nullable SQLXML xmlObject) throws SQLException { throw typeNotSupported(TYPE_SQLXML); } @Override public void updateSQLXML(String columnLabel, @Nullable SQLXML xmlObject) throws SQLException { throw typeNotSupported(TYPE_SQLXML); } @Override public @Nullable String getExecutionPlan() throws SQLException { checkCursorMove(); if (statement == null) return ""; return statement.getExecutionPlan(); } @Override public @Nullable String getExplainedExecutionPlan() throws SQLException { checkCursorMove(); if (statement == null) return ""; return statement.getExplainedExecutionPlan(); } @SuppressWarnings("ConstantValue") @Override public boolean isWrapperFor(Class iface) throws SQLException { return iface != null && iface.isAssignableFrom(this.getClass()); } @SuppressWarnings("ConstantValue") @Override public T unwrap(Class iface) throws SQLException { if (!isWrapperFor(iface)) { throw new SQLException("Unable to unwrap to class " + (iface != null ? iface.getName() : "(null)")); } return iface.cast(this); } private static SQLException typeNotSupported(String typeName) { return new FBDriverNotCapableException("Type " + typeName + " not supported"); } @SuppressWarnings("DataFlowIssue") private final class DataProvider implements FieldDataProvider { private final int fieldPosition; private DataProvider(int fieldPosition) { this.fieldPosition = fieldPosition; } @Override public byte[] getFieldData() { return row.getFieldData(fieldPosition); } @Override public void setFieldData(byte[] data) { row.setFieldData(fieldPosition, data); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy