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

org.firebirdsql.jdbc.field.FBField 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.field;

import org.firebirdsql.extern.decimal.*;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.DatatypeCoder;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.jaybird.util.LegacyDatetimeConversions;
import org.firebirdsql.jdbc.*;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.time.*;
import java.util.Calendar;
import java.util.Map;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;
import static org.firebirdsql.jdbc.JavaTypeNameConstants.*;
import static org.firebirdsql.jdbc.SQLStateConstants.SQL_STATE_INVALID_USE_NULL;

/**
 * Base class for fields (for use by prepared statement and result set to represent columns and parameters).
 *
 * @author Roman Rokytskyy
 * @author Mark Rotteveel
 */
@NullUnmarked
public abstract class FBField {

    static final String SQL_TYPE_NOT_SUPPORTED = "SQL type for this field is not yet supported";
    static final String SQL_ARRAY_NOT_SUPPORTED = "Types.ARRAY: " + FBField.SQL_TYPE_NOT_SUPPORTED;

    static final byte BYTE_NULL_VALUE = 0;
    static final short SHORT_NULL_VALUE = 0;
    static final int INT_NULL_VALUE = 0;
    static final long LONG_NULL_VALUE = 0;
    static final float FLOAT_NULL_VALUE = 0.0f;
    static final double DOUBLE_NULL_VALUE = 0.0;
    static final boolean BOOLEAN_NULL_VALUE = false;

    static final byte MAX_BYTE_VALUE = Byte.MAX_VALUE;
    static final byte MIN_BYTE_VALUE = Byte.MIN_VALUE;

    static final short MAX_SHORT_VALUE = Short.MAX_VALUE;
    static final short MIN_SHORT_VALUE = Short.MIN_VALUE;

    static final int MAX_INT_VALUE = Integer.MAX_VALUE;
    static final int MIN_INT_VALUE = Integer.MIN_VALUE;

    static final long MAX_LONG_VALUE = Long.MAX_VALUE;
    static final long MIN_LONG_VALUE = Long.MIN_VALUE;

    static final float MAX_FLOAT_VALUE = Float.MAX_VALUE;
    static final float MIN_FLOAT_VALUE = -1 * FBField.MAX_FLOAT_VALUE;

    static final double MAX_DOUBLE_VALUE = Double.MAX_VALUE;
    static final double MIN_DOUBLE_VALUE = -1 * FBField.MAX_DOUBLE_VALUE;

    protected final @NonNull FieldDescriptor fieldDescriptor;
    private final @NonNull FieldDataProvider dataProvider;
    protected GDSHelper gdsHelper;
    protected int requiredType;

    FBField(@NonNull FieldDescriptor fieldDescriptor, @NonNull FieldDataProvider dataProvider, int requiredType)
            throws SQLException {
        if (fieldDescriptor == null) {
            throw new SQLNonTransientException("Cannot create FBField instance with fieldDescriptor null",
                    SQL_STATE_INVALID_USE_NULL);
        }

        this.fieldDescriptor = fieldDescriptor;
        this.dataProvider = requireNonNull(dataProvider, "dataProvider");
        this.requiredType = requiredType;
    }

    protected final byte @Nullable [] getFieldData() {
        return dataProvider.getFieldData();
    }

    protected final void setFieldData(byte @Nullable [] data) {
        dataProvider.setFieldData(data);
    }

    protected final @NonNull DatatypeCoder getDatatypeCoder() {
        return fieldDescriptor.getDatatypeCoder();
    }

    /**
     * @return {@code true} if the corresponding field is {@code null}, otherwise {@code false}
     */
    @SuppressWarnings("java:S1130")
    public final boolean isNull() throws SQLException {
        return getFieldData() == null;
    }

    public void setNull() {
        setFieldData(null);
    }

    public final void setConnection(GDSHelper gdsHelper) {
        this.gdsHelper = gdsHelper;
    }

    /**
     * Set the required type for {@link #getObject()} conversion.
     *
     * @param requiredType
     *         required type, one of the {@link java.sql.Types} constants.
     */
    public void setRequiredType(int requiredType) {
        this.requiredType = requiredType;
    }

    /**
     * This is a factory method that creates appropriate instance of the
     * {@code FBField} class according to the SQL datatype. This instance
     * knows how to perform all necessary type conversions.
     */
    @NullMarked
    public static FBField createField(FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider,
            @Nullable GDSHelper gdsHelper, boolean cached) throws SQLException {
        final int jdbcType = JdbcTypeConverter.toJdbcType(fieldDescriptor);
        switch (jdbcType) {
        case Types.LONGVARCHAR -> {
            if (cached) {
                return new FBCachedLongVarCharField(fieldDescriptor, dataProvider, jdbcType, gdsHelper);
            } else {
                return new FBLongVarCharField(fieldDescriptor, dataProvider, jdbcType, gdsHelper);
            }
        }
        case Types.BLOB, Types.LONGVARBINARY -> {
            if (cached) {
                return new FBCachedBlobField(fieldDescriptor, dataProvider, jdbcType, gdsHelper);
            } else {
                return new FBBlobField(fieldDescriptor, dataProvider, jdbcType, gdsHelper);
            }
        }
        default -> {
            final FBField result = FBField.createField(jdbcType, fieldDescriptor, dataProvider);
            result.setConnection(gdsHelper);
            return result;
        }
        }
    }

    @NullMarked
    private static FBField createField(int jdbcType, FieldDescriptor fieldDescriptor, FieldDataProvider dataProvider)
            throws SQLException {
        return switch (jdbcType) {
            case Types.SMALLINT -> new FBShortField(fieldDescriptor, dataProvider, jdbcType);
            case Types.INTEGER -> new FBIntegerField(fieldDescriptor, dataProvider, jdbcType);
            case Types.BIGINT -> new FBLongField(fieldDescriptor, dataProvider, jdbcType);
            case Types.NUMERIC, Types.DECIMAL -> new FBBigDecimalField(fieldDescriptor, dataProvider, jdbcType);
            case JaybirdTypeCodes.DECFLOAT -> switch (fieldDescriptor.getType() & ~1) {
                case ISCConstants.SQL_DEC16 ->
                        new FBDecfloatField<>(fieldDescriptor, dataProvider, jdbcType, Decimal64.class);
                case ISCConstants.SQL_DEC34 ->
                        new FBDecfloatField<>(fieldDescriptor, dataProvider, jdbcType, Decimal128.class);
                default -> throw new FBDriverNotCapableException(
                        "Unexpected field type for DECFLOAT: " + fieldDescriptor.getType());
            };
            case Types.FLOAT -> new FBFloatField(fieldDescriptor, dataProvider, jdbcType);
            case Types.DOUBLE -> new FBDoubleField(fieldDescriptor, dataProvider, jdbcType);
            case Types.TIME -> new FBTimeField(fieldDescriptor, dataProvider, jdbcType);
            case Types.DATE -> new FBDateField(fieldDescriptor, dataProvider, jdbcType);
            case Types.TIMESTAMP -> new FBTimestampField(fieldDescriptor, dataProvider, jdbcType);
            case Types.TIMESTAMP_WITH_TIMEZONE -> new FBTimestampTzField(fieldDescriptor, dataProvider, jdbcType);
            case Types.TIME_WITH_TIMEZONE -> new FBTimeTzField(fieldDescriptor, dataProvider, jdbcType);
            case Types.CHAR, Types.VARCHAR -> new FBStringField(fieldDescriptor, dataProvider, jdbcType);
            case Types.VARBINARY, Types.BINARY -> new FBBinaryField(fieldDescriptor, dataProvider, jdbcType);
            case Types.BOOLEAN -> new FBBooleanField(fieldDescriptor, dataProvider, jdbcType);
            case Types.NULL -> new FBNullField(fieldDescriptor, dataProvider, jdbcType);
            case Types.ROWID -> new FBRowIdField(fieldDescriptor, dataProvider, jdbcType);
            case Types.ARRAY -> throw new FBDriverNotCapableException(FBField.SQL_ARRAY_NOT_SUPPORTED);
            default -> throw new FBDriverNotCapableException(FBField.SQL_TYPE_NOT_SUPPORTED);
        };
    }

    /**
     * Returns the name of the column as declared in the XSQLVAR.
     */
    public String getName() {
        return fieldDescriptor.getOriginalName();
    }

    /**
     * Returns the alias of the column as declared in XSQLVAR.
     */
    public String getAlias() {
        return fieldDescriptor.getFieldName();
    }

    /**
     * Returns the relation to which belongs column as declared in XSQLVAR.
     */
    public String getRelationName() {
        return fieldDescriptor.getOriginalTableName();
    }

    /*
     * All these methods simply throw an exception when invoked. All subclasses
     * should implement relevant methods with conversions.
     */

    // --- getters

    public byte getByte() throws SQLException {
        throw invalidGetConversion("byte");
    }

    public short getShort() throws SQLException {
        throw invalidGetConversion("short");
    }

    public int getInt() throws SQLException {
        throw invalidGetConversion("int");
    }

    public long getLong() throws SQLException {
        throw invalidGetConversion("long");
    }

    public float getFloat() throws SQLException {
        throw invalidGetConversion("float");
    }

    public double getDouble() throws SQLException {
        throw invalidGetConversion("double");
    }

    public BigDecimal getBigDecimal() throws SQLException {
        throw invalidGetConversion(BigDecimal.class);
    }

    @SuppressWarnings("unused")
    public final BigDecimal getBigDecimal(int scale) throws SQLException {
        return getBigDecimal();
    }

    public boolean getBoolean() throws SQLException {
        throw invalidGetConversion("boolean");
    }

    public String getString() throws SQLException {
        throw invalidGetConversion(String.class);
    }

    public Object getObject() throws SQLException {
        if (isNull()) return null;
        throw invalidGetConversion(Object.class);
    }

    @SuppressWarnings("unused")
    public Object getObject(Map> map) throws SQLException {
        throw new FBDriverNotCapableException();
    }

    @SuppressWarnings("java:S1479")
    public  T getObject(@NonNull Class type) throws SQLException {
        if (type == null) {
            throw new SQLNonTransientException("getObject called with type null");
        }
        Object result = switch (type.getName()) {
            case BOOLEAN_CLASS_NAME -> isNull() ? null : getBoolean();
            case BYTE_CLASS_NAME -> isNull() ? null : getByte();
            case SHORT_CLASS_NAME -> isNull() ? null : getShort();
            case INTEGER_CLASS_NAME -> isNull() ? null : getInt();
            case LONG_CLASS_NAME -> isNull() ? null : getLong();
            case FLOAT_CLASS_NAME -> isNull() ? null : getFloat();
            case DOUBLE_CLASS_NAME -> isNull() ? null : getDouble();
            case BIG_DECIMAL_CLASS_NAME -> getBigDecimal();
            case BIG_INTEGER_CLASS_NAME -> getBigInteger();
            case STRING_CLASS_NAME -> getString();
            case BYTE_ARRAY_CLASS_NAME -> getBytes();
            case SQL_DATE_CLASS_NAME -> getDate();
            case LOCAL_DATE_CLASS_NAME -> getLocalDate();
            case UTIL_DATE_CLASS_NAME, TIMESTAMP_CLASS_NAME -> getTimestamp();
            case LOCAL_DATE_TIME_CLASS_NAME -> getLocalDateTime();
            case TIME_CLASS_NAME -> getTime();
            case LOCAL_TIME_CLASS_NAME -> getLocalTime();
            case CALENDAR_CLASS_NAME -> {
                if (isNull()) {
                    yield null;
                } else {
                    Timestamp timestamp = getTimestamp();
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTimeInMillis(timestamp.getTime());
                    yield calendar;
                }
            }
            case OFFSET_TIME_CLASS_NAME -> getOffsetTime();
            case OFFSET_DATE_TIME_CLASS_NAME -> getOffsetDateTime();
            case ZONED_DATE_TIME_CLASS_NAME -> getZonedDateTime();
            case CLOB_CLASS_NAME, NCLOB_CLASS_NAME -> getClob();
            case BLOB_CLASS_NAME, FIREBIRD_BLOB_CLASS_NAME -> getBlob();
            case INPUT_STREAM_CLASS_NAME -> getBinaryStream();
            case READER_CLASS_NAME -> getCharacterStream();
            case ROW_ID_CLASS_NAME, FB_ROW_ID_CLASS_NAME -> getRowId();
            case DECIMAL_CLASS_NAME -> getDecimal();
            case DECIMAL32_CLASS_NAME -> getDecimal(Decimal32.class);
            case DECIMAL64_CLASS_NAME -> getDecimal(Decimal64.class);
            case DECIMAL128_CLASS_NAME -> getDecimal(Decimal128.class);
            default -> throw invalidGetConversion(type);
        };
        return type.cast(result);
    }

    private @NonNull String getJdbcTypeName() {
        return getJdbcTypeName(requiredType);
    }

    static @NonNull String getJdbcTypeName(int jdbcType) {
        if (jdbcType == JaybirdTypeCodes.DECFLOAT) {
            return "DECFLOAT";
        }
        try {
            return JDBCType.valueOf(jdbcType).name();
        } catch (IllegalArgumentException e) {
            return String.valueOf(jdbcType);
        }
    }

    public InputStream getBinaryStream() throws SQLException {
        throw invalidGetConversion(InputStream.class);
    }

    public Reader getCharacterStream() throws SQLException {
        final InputStream is = getBinaryStream();
        return is != null ? getDatatypeCoder().createReader(is) : null;
    }

    public byte[] getBytes() throws SQLException {
        throw invalidGetConversion("byte[]");
    }

    public Blob getBlob() throws SQLException {
        throw invalidGetConversion(Blob.class);
    }

    public Date getDate() throws SQLException {
        return getDate(null);
    }

    public Date getDate(Calendar cal) throws SQLException {
        return convertForGet(getLocalDate(), v -> LegacyDatetimeConversions.toDate(v, cal), Date.class);
    }

    LocalDate getLocalDate() throws SQLException {
        throw invalidGetConversion(LocalDate.class);
    }

    public Time getTime() throws SQLException {
        return getTime(null);
    }

    public Time getTime(Calendar cal) throws SQLException {
        return convertForGet(getLocalTime(), v -> LegacyDatetimeConversions.toTime(v, cal), Time.class);
    }

    LocalTime getLocalTime() throws SQLException {
        throw invalidGetConversion(LocalTime.class);
    }

    public Timestamp getTimestamp() throws SQLException {
        return getTimestamp(null);
    }

    public Timestamp getTimestamp(Calendar cal) throws SQLException {
        return convertForGet(getLocalDateTime(), v -> LegacyDatetimeConversions.toTimestamp(v, cal), Timestamp.class);
    }

    LocalDateTime getLocalDateTime() throws SQLException {
        throw invalidGetConversion(LocalDateTime.class);
    }

    OffsetTime getOffsetTime() throws SQLException {
        throw invalidGetConversion(OffsetTime.class);
    }

    OffsetDateTime getOffsetDateTime() throws SQLException {
        throw invalidGetConversion(OffsetDateTime.class);
    }

    ZonedDateTime getZonedDateTime() throws SQLException {
        throw invalidGetConversion(ZonedDateTime.class);
    }

    public Ref getRef() throws SQLException {
        throw new FBDriverNotCapableException("Type REF not supported");
    }

    public Clob getClob() throws SQLException {
        throw invalidGetConversion(Clob.class);
    }

    public Array getArray() throws SQLException {
        throw new FBDriverNotCapableException("Type ARRAY not yet supported");
    }

    public BigInteger getBigInteger() throws SQLException {
        throw invalidGetConversion(BigInteger.class);
    }

    public RowId getRowId() throws SQLException {
        throw invalidGetConversion(RowId.class);
    }

    // --- setters

    public void setByte(byte value) throws SQLException {
        throw invalidSetConversion("byte");
    }

    public void setShort(short value) throws SQLException {
        throw invalidSetConversion("short");
    }

    public void setInteger(int value) throws SQLException {
        throw invalidSetConversion("int");
    }

    public void setLong(long value) throws SQLException {
        throw invalidSetConversion("long");
    }

    public void setFloat(float value) throws SQLException {
        throw invalidSetConversion("float");
    }

    public void setDouble(double value) throws SQLException {
        throw invalidSetConversion("double");
    }

    public void setBigDecimal(BigDecimal value) throws SQLException {
        throw invalidSetConversion(BigDecimal.class);
    }

    public void setBoolean(boolean value) throws SQLException {
        throw invalidSetConversion("boolean");
    }

    public void setString(String value) throws SQLException {
        throw invalidSetConversion(String.class);
    }

    public void setBigInteger(BigInteger value) throws SQLException {
        throw invalidSetConversion(BigInteger.class);
    }

    public void setObject(Object value) throws SQLException {
        if (setWhenNull(value)) return;
        // As a form of optimization, we switch on the class name.
        // For non-final classes we'll also try using instanceof in the switch default.
        switch (value.getClass().getName()) {
        case BIG_DECIMAL_CLASS_NAME -> setBigDecimal((BigDecimal) value);
        case FB_BLOB_CLASS_NAME -> setBlob((FBBlob) value);
        case FB_CLOB_CLASS_NAME -> setClob((FBClob) value);
        case BOOLEAN_CLASS_NAME -> setBoolean((boolean) value);
        case BYTE_CLASS_NAME -> setByte((byte) value);
        case BYTE_ARRAY_CLASS_NAME -> setBytes((byte[]) value);
        case SQL_DATE_CLASS_NAME -> setDate((Date) value);
        case LOCAL_DATE_CLASS_NAME -> setLocalDate((LocalDate) value);
        case DOUBLE_CLASS_NAME -> setDouble((double) value);
        case FLOAT_CLASS_NAME -> setFloat((float) value);
        case INTEGER_CLASS_NAME -> setInteger((int) value);
        case LONG_CLASS_NAME -> setLong((long) value);
        case SHORT_CLASS_NAME -> setShort((short) value);
        case STRING_CLASS_NAME -> setString((String) value);
        case TIME_CLASS_NAME -> setTime((Time) value);
        case LOCAL_TIME_CLASS_NAME -> setLocalTime((LocalTime) value);
        case TIMESTAMP_CLASS_NAME -> setTimestamp((Timestamp) value);
        case LOCAL_DATE_TIME_CLASS_NAME -> setLocalDateTime((LocalDateTime) value);
        case OFFSET_TIME_CLASS_NAME -> setOffsetTime((OffsetTime) value);
        case OFFSET_DATE_TIME_CLASS_NAME -> setOffsetDateTime((OffsetDateTime) value);
        case ZONED_DATE_TIME_CLASS_NAME -> setZonedDateTime((ZonedDateTime) value);
        case UTIL_DATE_CLASS_NAME -> setTimestamp(new Timestamp(((java.util.Date) value).getTime()));
        case BIG_INTEGER_CLASS_NAME -> setBigInteger((BigInteger) value);
        case FB_ROW_ID_CLASS_NAME -> setRowId((RowId) value);
        case DECIMAL32_CLASS_NAME, DECIMAL64_CLASS_NAME, DECIMAL128_CLASS_NAME -> setDecimal((Decimal) value);
        default -> setObjectWithInstanceOf(value);
        }
    }

    public void setObject(Object value, int scaleOrLength) throws SQLException {
        if (value instanceof InputStream) {
            setBinaryStream((InputStream) value, scaleOrLength);
        } else if (value instanceof Reader) {
            setCharacterStream((Reader) value, scaleOrLength);
        } else {
            setObject(value);
        }
    }

    private void setObjectWithInstanceOf(Object value) throws SQLException {
        if (value instanceof BigDecimal bigDecimal) {
            setBigDecimal(bigDecimal);
        } else if (value instanceof BigInteger bigInteger) {
            setBigInteger(bigInteger);
        } else if (value instanceof RowId rowId) {
            setRowId(rowId);
        } else if (value instanceof InputStream inputStream) {
            setBinaryStream(inputStream);
        } else if (value instanceof Reader reader) {
            setCharacterStream(reader);
        } else if (value instanceof FBClob fbClob) {
            setClob(fbClob);
        } else if (value instanceof Clob clob) {
            setCharacterStream(clob.getCharacterStream());
        } else if (value instanceof FBBlob fbBlob) {
            setBlob(fbBlob);
        }  else if (value instanceof Blob blob) {
            setBinaryStream(blob.getBinaryStream());
        } else if (value instanceof Date date) {
            setDate(date);
        } else if (value instanceof Time time) {
            setTime(time);
        } else if (value instanceof Timestamp timestamp) {
            setTimestamp(timestamp);
        } else if (value instanceof java.util.Date juDate) {
            setTimestamp(new Timestamp(juDate.getTime()));
        } else if (value instanceof Decimal decimal) {
            setDecimal(decimal);
        } else {
            throw invalidSetConversion(value.getClass());
        }
    }

    protected void setBinaryStreamInternal(InputStream in, long length) throws SQLException {
        throw invalidSetConversion(InputStream.class);
    }

    public final void setBinaryStream(InputStream in, long length) throws SQLException {
        if (length < 0) {
            throw new SQLNonTransientException("Length needs to be >= 0, was: " + length);
        }
        setBinaryStreamInternal(in, length);
    }

    public final void setBinaryStream(InputStream in) throws SQLException {
        setBinaryStreamInternal(in, -1L);
    }

    public final void setBinaryStream(InputStream in, int length) throws SQLException {
        setBinaryStream(in, (long) length);
    }

    protected void setCharacterStreamInternal(Reader in, long length) throws SQLException {
        throw invalidSetConversion(Reader.class);
    }

    public final void setCharacterStream(Reader in, long length) throws SQLException {
        if (length < 0) {
            throw new SQLNonTransientException("Length needs to be >= 0, was: " + length);
        }
        setCharacterStreamInternal(in, length);
    }

    public final void setCharacterStream(Reader in) throws SQLException {
        setCharacterStreamInternal(in, -1L);
    }

    public final void setCharacterStream(Reader in, int length) throws SQLException {
        setCharacterStream(in, (long) length);
    }

    public void setBytes(byte[] value) throws SQLException {
        throw invalidSetConversion("byte[]");
    }

    public void setDate(Date value, Calendar cal) throws SQLException {
        setLocalDate(convertForSet(value, v -> LegacyDatetimeConversions.toLocalDate(v, cal), Date.class));
    }

    public void setDate(Date value) throws SQLException {
        setDate(value, null);
    }

    void setLocalDate(LocalDate value) throws SQLException {
        throw invalidSetConversion(LocalDate.class);
    }

    public void setTime(Time value, Calendar cal) throws SQLException {
        setLocalTime(convertForSet(value, v -> LegacyDatetimeConversions.toLocalTime(v, cal), Time.class));
    }

    public void setTime(Time value) throws SQLException {
        setTime(value, null);
    }

    void setLocalTime(LocalTime value) throws SQLException {
        throw invalidSetConversion(LocalTime.class);
    }

    public void setTimestamp(Timestamp value, Calendar cal) throws SQLException {
        setLocalDateTime(convertForSet(value, v -> LegacyDatetimeConversions.toLocalDateTime(v, cal), Timestamp.class));
    }

    public void setTimestamp(Timestamp value) throws SQLException {
        setTimestamp(value, null);
    }

    void setLocalDateTime(LocalDateTime value) throws SQLException {
        throw invalidSetConversion(LocalDateTime.class);
    }

    void setOffsetTime(OffsetTime value) throws SQLException {
        throw invalidSetConversion(OffsetTime.class);
    }

    void setOffsetDateTime(OffsetDateTime value) throws SQLException {
        throw invalidSetConversion(OffsetDateTime.class);
    }

    void setZonedDateTime(ZonedDateTime value) throws SQLException {
        throw invalidSetConversion(ZonedDateTime.class);
    }

    public void setBlob(FBBlob blob) throws SQLException {
        throw invalidSetConversion(Blob.class);
    }

    public void setBlob(Blob blob) throws SQLException {
        throw invalidSetConversion(Blob.class);
    }

    @NonNull FBBlob createBlob() throws SQLException {
        throw invalidSetConversion(Blob.class);
    }

    public void setClob(FBClob clob) throws SQLException {
        throw invalidSetConversion(Clob.class);
    }

    public void setClob(Clob clob) throws SQLException {
        throw invalidSetConversion(Clob.class);
    }

    final @NonNull FBClob createClob() throws SQLException {
        return new FBClob(createBlob());
    }

    public void setRowId(RowId rowId) throws SQLException {
        throw invalidSetConversion(RowId.class);
    }

    /**
     * Sets the field to {@code NULL}, when {@code value} is {@code null}.
     *
     * @param value
     *         Value to check for {@code null}
     * @return {@code true} when {@code value} was {@code null}, {@code false} otherwise
     */
    final boolean setWhenNull(Object value) {
        if (value != null) return false;
        setNull();
        return true;
    }

    /**
     * Returns the value as a Decimal type.
     * 

* The default for this method is implemented in terms of {@link #getBigDecimal()}, and * returning a {@link Decimal128}. Implementations may return a {@link Decimal64} (or even * {@link Decimal32}). *

* * @return The value as decimal * @throws SQLException * For database access errors, or values that cannot be converted. */ @SuppressWarnings("java:S1452") public Decimal getDecimal() throws SQLException { return convertForGet(getBigDecimal(), v -> Decimal128.valueOf(v, OverflowHandling.THROW_EXCEPTION), Decimal128.class, "value %s out of range"::formatted); } public final > D getDecimal(@NonNull Class targetType) throws SQLException { return convertForGet(getDecimal(), v -> v.toDecimal(targetType, OverflowHandling.THROW_EXCEPTION), targetType, "value %s out of range"::formatted); } /** * Sets the value as a Decimal type. *

* The default for this method is implemented in terms of {@link #setBigDecimal(BigDecimal)}. *

* * @param decimal * Value to set */ public void setDecimal(Decimal decimal) throws SQLException { setBigDecimal(convertForSet(decimal, v -> decimal.toBigDecimal(), Decimal.class, "value %s out of range"::formatted)); } @NullMarked final SQLException invalidGetConversion(Class requestedType) { return invalidGetConversion(requestedType, null, null); } @NullMarked final SQLException invalidGetConversion(Class requestedType, @Nullable String reason) { return invalidGetConversion(requestedType, reason, null); } @NullMarked final SQLException invalidGetConversion(Class requestedType, @Nullable String reason, @Nullable Throwable cause) { return invalidGetConversion(requestedType.getName(), reason, cause); } @NullMarked final SQLException invalidGetConversion(String requestedTypeName) { return invalidGetConversion(requestedTypeName, null, null); } @NullMarked final SQLException invalidGetConversion(String requestedTypeName, @Nullable String reason) { return invalidGetConversion(requestedTypeName, reason, null); } @NullMarked final SQLException invalidGetConversion(String requestedTypeName, @Nullable String reason, @Nullable Throwable cause) { String message = String.format( "Unsupported get conversion requested for field %s at index %d (JDBC type %s), target type: %s", getAlias(), fieldDescriptor.getPosition() + 1, getJdbcTypeName(), requestedTypeName); if (reason != null) { message = message + ", reason: " + reason; } return cause != null ? new TypeConversionException(message, cause) : new TypeConversionException(message); } @NullMarked final SQLException invalidSetConversion(Class sourceType) { return invalidSetConversion(sourceType, null, null); } @NullMarked final SQLException invalidSetConversion(Class sourceType, @Nullable Throwable cause) { return invalidSetConversion(sourceType, null, cause); } @NullMarked final SQLException invalidSetConversion(Class sourceType, @Nullable String reason) { return invalidSetConversion(sourceType, reason, null); } @NullMarked final SQLException invalidSetConversion(Class sourceType, @Nullable String reason, @Nullable Throwable cause) { return invalidSetConversion(sourceType.getName(), reason, cause); } @NullMarked final SQLException invalidSetConversion(String sourceTypeName) { return invalidSetConversion(sourceTypeName, null, null); } @NullMarked final SQLException invalidSetConversion(String sourceTypeName, @Nullable String reason) { return invalidSetConversion(sourceTypeName, reason, null); } @NullMarked final SQLException invalidSetConversion(String sourceTypeName, @Nullable String reason, @Nullable Throwable cause) { String message = "Unsupported set conversion requested for field %s at index %d (JDBC type %s), source type: %s" .formatted(getAlias(), fieldDescriptor.getPosition() + 1, getJdbcTypeName(), sourceTypeName); if (reason != null) { message = message + ", reason: " + reason; } return cause != null ? new TypeConversionException(message, cause) : new TypeConversionException(message); } final T fromString(String value, @NonNull Function converter) throws SQLException { return convertForSet(value, converter.compose(String::trim), String.class); } final T convertForSet(S value, @NonNull Function converter, @NonNull Class sourceType) throws SQLException { return convertForSet(value, converter, sourceType, String::valueOf); } final T convertForSet(S value, @NonNull Function converter, @NonNull Class sourceType, @NonNull Function reasonFactory) throws SQLException { if (value == null) return null; try { return converter.apply(value); } catch (RuntimeException e) { throw invalidSetConversion(sourceType, reasonFactory.apply(value), e); } } final T convertForGet(S value, @NonNull Function converter, @NonNull Class requestedType) throws SQLException { return convertForGet(value, converter, requestedType, String::valueOf); } final T convertForGet(S value, @NonNull Function converter, @NonNull Class requestedType, @NonNull Function reasonFactory) throws SQLException { if (value == null) return null; try { return converter.apply(value); } catch (RuntimeException e) { throw invalidGetConversion(requestedType, reasonFactory.apply(value), e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy