HHH-15463 Adjust JdbcType based on DDL capacity for varchar/varbinary like types

This commit is contained in:
Christian Beikov 2022-08-25 16:12:49 +02:00
parent 66b86ad315
commit a094d4c5d5
25 changed files with 517 additions and 73 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate.userguide.mapping.basic;
import java.time.Duration;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@ -18,7 +19,9 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -50,15 +53,25 @@ public class DurationMappingTests {
final JdbcType intervalType = jdbcTypeRegistry.getDescriptor(SqlTypes.INTERVAL_SECOND);
final JdbcType realType;
if (intervalType instanceof AdjustableJdbcType) {
realType = ((AdjustableJdbcType) intervalType).resolveIndicatedType(
() -> mappingMetamodel.getTypeConfiguration(),
realType = ( (AdjustableJdbcType) intervalType ).resolveIndicatedType(
new JdbcTypeIndicators() {
@Override
public TypeConfiguration getTypeConfiguration() {
return mappingMetamodel.getTypeConfiguration();
}
@Override
public int getColumnScale() {
return duration.getScale() == null ? JdbcTypeIndicators.NO_COLUMN_SCALE : duration.getScale();
}
},
jdbcMapping.getJavaTypeDescriptor()
);
}
else {
realType = intervalType;
}
assertThat( jdbcMapping.getJdbcType(), is( realType));
assertThat( jdbcMapping.getJdbcType(), is( realType ) );
scope.inTransaction(
(session) -> {
@ -69,6 +82,14 @@ public class DurationMappingTests {
scope.inTransaction(
(session) -> session.find(EntityWithDuration.class, 1)
);
scope.inTransaction(
(session) -> {
session.createQuery( "from EntityWithDuration e where e.duration = :param", EntityWithDuration.class )
.setParameter( "param", Duration.ofHours( 3 ) )
.getResultList();
}
);
}
@Entity(name = "EntityWithDuration")

View File

@ -39,7 +39,7 @@ import static org.hamcrest.Matchers.is;
public class InetAddressMappingTests {
@Test
public void verifyMappings(SessionFactoryScope scope) {
public void verifyMappings(SessionFactoryScope scope) throws Exception {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
@ -62,20 +62,27 @@ public class InetAddressMappingTests {
}
assertThat( jdbcMapping.getJdbcType(), is( realType));
EntityWithInetAddress entity = new EntityWithInetAddress( 1, InetAddress.getLocalHost() );
scope.inTransaction(
(session) -> {
try {
session.persist( new EntityWithInetAddress( 1, InetAddress.getLocalHost() ) );
}
catch (UnknownHostException e) {
throw new RuntimeException( e );
}
session.persist( entity );
}
);
scope.inTransaction(
(session) -> session.find( EntityWithInetAddress.class, 1)
);
scope.inTransaction(
(session) -> {
session.createQuery(
"from EntityWithInetAddress e where e.address = :param",
EntityWithInetAddress.class
)
.setParameter( "param", entity.address )
.getResultList();
}
);
}
@Entity(name = "EntityWithInetAddress")

View File

@ -168,6 +168,11 @@ public class InformixDialect extends Dialect {
);
}
@Override
public boolean useMaterializedLobWhenCapacityExceeded() {
return false;
}
@Override
public int getMaxVarbinaryLength() {
//there's no varbinary type, only byte

View File

@ -158,6 +158,11 @@ public class RDMSOS2200Dialect extends Dialect {
}
}
@Override
public boolean useMaterializedLobWhenCapacityExceeded() {
return false;
}
@Override
public int getMaxVarbinaryLength() {
//no varbinary type

View File

@ -84,6 +84,11 @@ public class SybaseAnywhereDialect extends SybaseDialect {
}
}
@Override
public boolean useMaterializedLobWhenCapacityExceeded() {
return false;
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );

View File

@ -196,7 +196,17 @@ public class BasicValueBinder implements JdbcTypeIndicators {
@Override
public boolean isLob() {
return isLob;
if ( isLob ) {
return true;
}
if ( explicitJdbcTypeAccess != null ) {
final JdbcType type = explicitJdbcTypeAccess.apply( getTypeConfiguration() );
if ( type != null ) {
return type.isLob();
}
}
return false;
}
@Override
@ -239,7 +249,16 @@ public class BasicValueBinder implements JdbcTypeIndicators {
@Override
public boolean isNationalized() {
return isNationalized;
if ( isNationalized ) {
return true;
}
if ( explicitJdbcTypeAccess != null ) {
final JdbcType type = explicitJdbcTypeAccess.apply( getTypeConfiguration() );
if ( type != null ) {
return type.isNationalized();
}
}
return false;
}
@ -1093,11 +1112,11 @@ public class BasicValueBinder implements JdbcTypeIndicators {
basicValue = new BasicValue( buildingContext, table );
if ( isNationalized ) {
if ( isNationalized() ) {
basicValue.makeNationalized();
}
if ( isLob ) {
if ( isLob() ) {
basicValue.makeLob();
}
@ -1243,11 +1262,11 @@ public class BasicValueBinder implements JdbcTypeIndicators {
basicValue.setTemporalPrecision( temporalPrecision );
}
if ( isLob ) {
if ( isLob() ) {
basicValue.makeLob();
}
if ( isNationalized ) {
if ( isNationalized() ) {
basicValue.makeNationalized();
}

View File

@ -104,9 +104,13 @@ public class InferredBasicValueResolver {
jdbcMapping = new SerializableType( explicitJavaType );
}
else {
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve(
explicitJavaType,
inferredJdbcType
jdbcMapping = resolveSqlTypeIndicators(
stdIndicators,
typeConfiguration.getBasicTypeRegistry().resolve(
explicitJavaType,
inferredJdbcType
),
explicitJavaType
);
}
}
@ -206,9 +210,13 @@ public class InferredBasicValueResolver {
// one, to create a mapping
final JdbcType recommendedJdbcType = reflectedJtd.getRecommendedJdbcType( stdIndicators );
if ( recommendedJdbcType != null ) {
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve(
reflectedJtd,
recommendedJdbcType
jdbcMapping = resolveSqlTypeIndicators(
stdIndicators,
typeConfiguration.getBasicTypeRegistry().resolve(
reflectedJtd,
recommendedJdbcType
),
reflectedJtd
);
}
else if ( reflectedJtd instanceof SerializableJavaType

View File

@ -450,6 +450,10 @@ public class MetadataBuildingProcess {
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( XmlAsStringJdbcType.INSTANCE );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.MATERIALIZED_BLOB, SqlTypes.BLOB );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.MATERIALIZED_CLOB, SqlTypes.CLOB );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.MATERIALIZED_NCLOB, SqlTypes.NCLOB );
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
// Fallback to the biggest varchar DdlType when json is requested
ddlTypeRegistry.addDescriptorIfAbsent(

View File

@ -168,49 +168,42 @@ public class DerbyDialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
//long varchar is the right type to use for lengths between 32_672 and 32_700
int maxLongVarcharLength = 32_700;
int varcharDdlTypeCapacity = 32_672;
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( VARBINARY, columnType( BLOB ), columnType( VARBINARY ), this )
.withTypeCapacity( getMaxVarbinaryLength(), columnType( VARBINARY ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARBINARY ) )
CapacityDependentDdlType.builder( VARBINARY, columnType( LONG32VARBINARY ), columnType( VARBINARY ), this )
.withTypeCapacity( varcharDdlTypeCapacity, columnType( VARBINARY ) )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( VARCHAR, columnType( CLOB ), columnType( VARCHAR ), this )
.withTypeCapacity( getMaxVarcharLength(), columnType( VARCHAR ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) )
CapacityDependentDdlType.builder( VARCHAR, columnType( LONG32VARCHAR ), columnType( VARCHAR ), this )
.withTypeCapacity( varcharDdlTypeCapacity, columnType( VARCHAR ) )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( NVARCHAR, columnType( CLOB ), columnType( NVARCHAR ), this )
.withTypeCapacity( getMaxVarcharLength(), columnType( NVARCHAR ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) )
CapacityDependentDdlType.builder( NVARCHAR, columnType( LONG32VARCHAR ), columnType( NVARCHAR ), this )
.withTypeCapacity( varcharDdlTypeCapacity, columnType( NVARCHAR ) )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( BINARY, columnType( BLOB ), columnType( VARBINARY ), this )
CapacityDependentDdlType.builder( BINARY, columnType( LONG32VARBINARY ), columnType( VARBINARY ), this )
.withTypeCapacity( 254, "char($l) for bit data" )
.withTypeCapacity( getMaxVarbinaryLength(), columnType( VARBINARY ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARBINARY ) )
.withTypeCapacity( varcharDdlTypeCapacity, columnType( VARBINARY ) )
.build()
);
// This is the maximum size for the CHAR datatype on Derby
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( CHAR, columnType( CLOB ), columnType( CHAR ), this )
CapacityDependentDdlType.builder( CHAR, columnType( LONG32VARCHAR ), columnType( CHAR ), this )
.withTypeCapacity( 254, columnType( CHAR ) )
.withTypeCapacity( getMaxVarcharLength(), columnType( VARCHAR ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( NCHAR, columnType( CLOB ), columnType( NCHAR ), this )
CapacityDependentDdlType.builder( NCHAR, columnType( LONG32NVARCHAR ), columnType( NCHAR ), this )
.withTypeCapacity( 254, columnType( NCHAR ) )
.withTypeCapacity( getMaxVarcharLength(), columnType( NVARCHAR ) )
.withTypeCapacity( maxLongVarcharLength, columnType( LONG32NVARCHAR ) )
.build()
);
}
@ -220,6 +213,11 @@ public class DerbyDialect extends Dialect {
return 32_672;
}
@Override
public int getMaxVarcharCapacity() {
return 32_700;
}
@Override
public int getDefaultDecimalPrecision() {
//this is the maximum allowed in Derby

View File

@ -171,6 +171,7 @@ import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
@ -1396,29 +1397,35 @@ public abstract class Dialect implements ConversionContext {
// by default, not much to do...
registerColumnTypes( typeContributions, serviceRegistry );
final NationalizationSupport nationalizationSupport = getNationalizationSupport();
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
if ( nationalizationSupport == NationalizationSupport.EXPLICIT ) {
typeContributions.contributeJdbcType( NCharJdbcType.INSTANCE );
typeContributions.contributeJdbcType( NVarcharJdbcType.INSTANCE );
typeContributions.contributeJdbcType( LongNVarcharJdbcType.INSTANCE );
typeContributions.contributeJdbcType( NClobJdbcType.DEFAULT );
jdbcTypeRegistry.addDescriptor( NCharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( NVarcharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( LongNVarcharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( NClobJdbcType.DEFAULT );
}
if ( useInputStreamToInsertBlob() ) {
typeContributions.getTypeConfiguration().getJdbcTypeRegistry().addDescriptor(
jdbcTypeRegistry.addDescriptor(
Types.CLOB,
ClobJdbcType.STREAM_BINDING
);
}
if ( getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
typeContributions.contributeJdbcType( InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
}
else {
typeContributions.contributeJdbcType( InstantAsTimestampJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( InstantAsTimestampJdbcType.INSTANCE );
}
if ( supportsStandardArrays() ) {
typeContributions.contributeJdbcType( ArrayJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( ArrayJdbcType.INSTANCE );
}
if ( supportsMaterializedLobAccess() ) {
jdbcTypeRegistry.addDescriptor( SqlTypes.MATERIALIZED_BLOB, BlobJdbcType.MATERIALIZED );
jdbcTypeRegistry.addDescriptor( SqlTypes.MATERIALIZED_CLOB, ClobJdbcType.MATERIALIZED );
jdbcTypeRegistry.addDescriptor( SqlTypes.MATERIALIZED_NCLOB, NClobJdbcType.MATERIALIZED );
}
}
@ -3816,6 +3823,32 @@ public abstract class Dialect implements ConversionContext {
return true;
}
/**
* Check whether the JDBC driver allows setting LOBs via {@link PreparedStatement#setBytes(int, byte[])},
* {@link PreparedStatement#setNString(int, String)} or {@link PreparedStatement#setString(int, String)} APIs.
*
* @return {@code true} if LOBs can be set with the materialized APIs.
* @since 6.2
*/
public boolean supportsMaterializedLobAccess() {
// Most drivers support this
return true;
}
/**
* Whether to switch from {@code VARCHAR}-like types to {@link SqlTypes#MATERIALIZED_CLOB},
* {@code NVARCHAR}-like types to {@link SqlTypes#MATERIALIZED_NCLOB}
* and {@code VARBINARY}-like types to {@link SqlTypes#MATERIALIZED_BLOB} types,
* when the requested size for a type exceeds the {@link #getMaxVarcharCapacity()}, {@link #getMaxNVarcharCapacity()}
* and {@link #getMaxVarbinaryCapacity()} respectively.
*
* @return {@code true} if materialized LOBs should be used for capacity exceeding types.
* @since 6.2
*/
public boolean useMaterializedLobWhenCapacityExceeded() {
return supportsMaterializedLobAccess();
}
/**
* Modify the SQL, adding hints or comments, if necessary
*/
@ -3895,7 +3928,7 @@ public abstract class Dialect implements ConversionContext {
}
/**
* The longest possible length of a {@link java.sql.Types#VARCHAR}-like column.
* The biggest size value that can be supplied as argument to a {@link java.sql.Types#VARCHAR}-like type.
* For longer column lengths, use some sort of {@code text}-like type for the
* column.
*/
@ -3905,8 +3938,8 @@ public abstract class Dialect implements ConversionContext {
}
/**
* The longest possible length of a {@link java.sql.Types#NVARCHAR}-like column.
* For longer column lengths, use some sort of {@code text}-like type for the
* The biggest size value that can be supplied as argument to a {@link java.sql.Types#NVARCHAR}-like type.
* For longer column lengths, use some sort of {@code ntext}-like type for the
* column.
*/
public int getMaxNVarcharLength() {
@ -3915,7 +3948,7 @@ public abstract class Dialect implements ConversionContext {
}
/**
* The longest possible length of a {@link java.sql.Types#VARBINARY}-like column.
* The biggest size value that can be supplied as argument to a {@link java.sql.Types#VARBINARY}-like type.
* For longer column lengths, use some sort of {@code image}-like type for the
* column.
*/
@ -3924,6 +3957,33 @@ public abstract class Dialect implements ConversionContext {
return getMaxVarcharLength();
}
/**
* The longest possible length of a {@link java.sql.Types#VARCHAR}-like column.
* For longer column lengths, use some sort of {@code clob}-like type for the
* column.
*/
public int getMaxVarcharCapacity() {
return getMaxVarcharLength();
}
/**
* The longest possible length of a {@link java.sql.Types#NVARCHAR}-like column.
* For longer column lengths, use some sort of {@code nclob}-like type for the
* column.
*/
public int getMaxNVarcharCapacity() {
return getMaxNVarcharLength();
}
/**
* The longest possible length of a {@link java.sql.Types#VARBINARY}-like column.
* For longer column lengths, use some sort of {@code blob}-like type for the
* column.
*/
public int getMaxVarbinaryCapacity() {
return getMaxVarbinaryLength();
}
public long getDefaultLobLength() {
return Size.DEFAULT_LOB_LENGTH;
}

View File

@ -242,6 +242,12 @@ public class MySQLDialect extends Dialect {
}
}
@Override
public boolean useMaterializedLobWhenCapacityExceeded() {
// MySQL has no real concept of LOBs, so we can just use longtext/longblob with the materialized JDBC APIs
return false;
}
@Override
protected String castType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
@ -304,9 +310,9 @@ public class MySQLDialect extends Dialect {
"char",
this
)
.withTypeCapacity( getMaxVarcharLength(), "varchar($l)" )
.withTypeCapacity( getVarcharDdlTypeCapacity(), "varchar($l)" )
.withTypeCapacity( maxMediumLobLen, "mediumtext" );
if ( getMaxVarcharLength() < maxLobLen ) {
if ( getVarcharDdlTypeCapacity() < maxLobLen ) {
varcharBuilder.withTypeCapacity( maxLobLen, "text" );
}
ddlTypeRegistry.addDescriptor( varcharBuilder.build() );
@ -317,9 +323,9 @@ public class MySQLDialect extends Dialect {
"char",
this
)
.withTypeCapacity( getMaxVarcharLength(), "varchar($l)" )
.withTypeCapacity( getVarcharDdlTypeCapacity(), "varchar($l)" )
.withTypeCapacity( maxMediumLobLen, "mediumtext" );
if ( getMaxVarcharLength() < maxLobLen ) {
if ( getVarcharDdlTypeCapacity() < maxLobLen ) {
nvarcharBuilder.withTypeCapacity( maxLobLen, "text" );
}
ddlTypeRegistry.addDescriptor( nvarcharBuilder.build() );
@ -330,9 +336,9 @@ public class MySQLDialect extends Dialect {
"binary",
this
)
.withTypeCapacity( getMaxVarbinaryLength(), "varbinary($l)" )
.withTypeCapacity( getVarbinaryDdlTypeCapacity(), "varbinary($l)" )
.withTypeCapacity( maxMediumLobLen, "mediumblob" );
if ( getMaxVarcharLength() < maxLobLen ) {
if ( getVarbinaryDdlTypeCapacity() < maxLobLen ) {
varbinaryBuilder.withTypeCapacity( maxLobLen, "blob" );
}
ddlTypeRegistry.addDescriptor( varbinaryBuilder.build() );
@ -425,13 +431,11 @@ public class MySQLDialect extends Dialect {
}
}
@Override
public int getMaxVarcharLength() {
public int getVarcharDdlTypeCapacity() {
return maxVarcharLength;
}
@Override
public int getMaxVarbinaryLength() {
public int getVarbinaryDdlTypeCapacity() {
return maxVarbinaryLength;
}

View File

@ -261,6 +261,12 @@ public class PostgreSQLDialect extends Dialect {
return 10_485_760;
}
@Override
public int getMaxVarcharCapacity() {
// 1GB according to PostgreSQL docs
return 1_073_741_824;
}
@Override
public int getMaxVarbinaryLength() {
//postgres has no varbinary-like type
@ -1001,6 +1007,13 @@ public class PostgreSQLDialect extends Dialect {
return false;
}
@Override
public boolean supportsMaterializedLobAccess() {
// Prefer using text and bytea over oid (LOB), because oid is very restricted.
// If someone really wants a type bigger than 1GB, they should ask for it by using @Lob explicitly
return false;
}
@Override
public boolean supportsTemporalLiteralOffset() {
return true;

View File

@ -17,6 +17,7 @@ import java.sql.Types;
import java.time.Duration;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
@ -29,7 +30,6 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Christian Beikov
@ -104,8 +104,22 @@ public class PostgreSQLIntervalSecondJdbcType implements AdjustableJdbcType {
@Override
public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<?> domainJtd) {
// The default scale is 9
if ( indicators.getColumnScale() == JdbcTypeIndicators.NO_COLUMN_SCALE || indicators.getColumnScale() > 6 ) {
final int scale;
if ( indicators.getColumnScale() == JdbcTypeIndicators.NO_COLUMN_SCALE ) {
scale = domainJtd.getDefaultSqlScale(
indicators.getTypeConfiguration()
.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
this
);
}
else {
scale = indicators.getColumnScale();
}
if ( scale > 6 ) {
// Since the maximum allowed scale on PostgreSQL is 6 (microsecond precision),
// we have to switch to the numeric type if the value is greater
return indicators.getJdbcType( indicators.resolveJdbcTypeCode( SqlTypes.NUMERIC ) );
}
return this;

View File

@ -10,6 +10,8 @@ import org.hibernate.Internal;
import java.sql.Types;
import org.hibernate.Internal;
/**
* Defines a list of constant type codes used to identify generic SQL types.
* This is an extension of the standard JDBC-defined {@link Types}, defining
@ -446,6 +448,39 @@ public class SqlTypes {
*/
public static final int TIMESTAMP_UTC = 3003;
/**
* The constant in the Java programming language, sometimes referred to
* as a type code, that identifies the generic SQL type
* {@code MATERIALIZED_BLOB}.
*
* This type is used when JDBC access should use {@link #VARBINARY} semantics,
* but the {@link org.hibernate.type.descriptor.sql.DdlType} should be based on {@link #BLOB}.
*/
@Internal
public static final int MATERIALIZED_BLOB = 3004;
/**
* The constant in the Java programming language, sometimes referred to
* as a type code, that identifies the generic SQL type
* {@code MATERIALIZED_CLOB}.
*
* This type is used when JDBC access should use {@link #VARCHAR} semantics,
* but the {@link org.hibernate.type.descriptor.sql.DdlType} should be based on {@link #CLOB}.
*/
@Internal
public static final int MATERIALIZED_CLOB = 3005;
/**
* The constant in the Java programming language, sometimes referred to
* as a type code, that identifies the generic SQL type
* {@code MATERIALIZED_NCLOB}.
*
* This type is used when JDBC access should use {@link #NVARCHAR} semantics,
* but the {@link org.hibernate.type.descriptor.sql.DdlType} should be based on {@link #NCLOB}.
*/
@Internal
public static final int MATERIALIZED_NCLOB = 3006;
// Interval types
/**

View File

@ -225,4 +225,54 @@ public abstract class BlobJdbcType implements JdbcType {
}
};
public static final BlobJdbcType MATERIALIZED = new BlobJdbcType() {
@Override
public String toString() {
return "BlobTypeDescriptor(MATERIALIZED)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return byte[].class;
}
@Override
public <X> BasicBinder<X> getBlobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
public void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setBytes( index, javaType.unwrap( value, byte[].class, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setBytes( name, javaType.unwrap( value, byte[].class, options ) );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getBytes( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getBytes( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
throws SQLException {
return javaType.wrap( statement.getBytes( name ), options );
}
};
}
};
}

View File

@ -305,4 +305,55 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
}
};
public static final ClobJdbcType MATERIALIZED = new ClobJdbcType() {
@Override
public String toString() {
return "ClobTypeDescriptor(MATERIALIZED)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setString( index, javaType.unwrap( value, String.class, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setString( name, javaType.unwrap( value, String.class, options ) );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getString( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options)
throws SQLException {
return javaType.wrap( statement.getString( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
throws SQLException {
return javaType.wrap( statement.getString( name ), options );
}
};
}
};
}

View File

@ -192,6 +192,23 @@ public interface JdbcType extends Serializable {
return false;
}
default boolean isNationalized() {
return isNationalized( getJdbcTypeCode() );
}
static boolean isNationalized(int jdbcTypeCode) {
switch ( jdbcTypeCode ) {
case SqlTypes.NCHAR:
case SqlTypes.NVARCHAR:
case SqlTypes.LONGNVARCHAR:
case SqlTypes.LONG32NVARCHAR:
case SqlTypes.NCLOB: {
return true;
}
}
return false;
}
default boolean isInterval() {
return SqlTypes.isIntervalType( getDefaultSqlTypeCode() );
}

View File

@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.jdbc;
import java.sql.Types;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
@ -46,6 +47,9 @@ public class LongNVarcharJdbcType extends NVarcharJdbcType {
if ( indicators.isLob() ) {
jdbcTypeCode = indicators.isNationalized() ? Types.NCLOB : Types.CLOB;
}
else if ( shouldUseMaterializedLob( indicators ) ) {
jdbcTypeCode = indicators.isNationalized() ? SqlTypes.MATERIALIZED_NCLOB : SqlTypes.MATERIALIZED_CLOB;
}
else {
jdbcTypeCode = indicators.isNationalized() ? Types.LONGNVARCHAR : Types.LONGVARCHAR;
}

View File

@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.jdbc;
import java.sql.Types;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
@ -53,6 +54,9 @@ public class LongVarcharJdbcType extends VarcharJdbcType {
if ( indicators.isLob() ) {
jdbcTypeCode = indicators.isNationalized() ? Types.NCLOB : Types.CLOB;
}
else if ( shouldUseMaterializedLob( indicators ) ) {
jdbcTypeCode = indicators.isNationalized() ? SqlTypes.MATERIALIZED_NCLOB : SqlTypes.MATERIALIZED_CLOB;
}
else {
jdbcTypeCode = indicators.isNationalized() ? Types.LONGNVARCHAR : Types.LONGVARCHAR;
}

View File

@ -179,4 +179,55 @@ public abstract class NClobJdbcType implements JdbcType {
};
}
};
public static final NClobJdbcType MATERIALIZED = new NClobJdbcType() {
@Override
public String toString() {
return "NClobTypeDescriptor(MATERIALIZED)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;
}
@Override
public <X> BasicBinder<X> getNClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setNString( index, javaType.unwrap( value, String.class, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setNString( name, javaType.unwrap( value, String.class, options ) );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getNString( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options)
throws SQLException {
return javaType.wrap( statement.getNString( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
throws SQLException {
return javaType.wrap( statement.getNString( name ), options );
}
};
}
};
}

View File

@ -12,6 +12,9 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
@ -77,6 +80,9 @@ public class NVarcharJdbcType implements AdjustableJdbcType {
if ( indicators.isLob() ) {
jdbcTypeCode = indicators.isNationalized() ? Types.NCLOB : Types.CLOB;
}
else if ( shouldUseMaterializedLob( indicators ) ) {
jdbcTypeCode = indicators.isNationalized() ? SqlTypes.MATERIALIZED_NCLOB : SqlTypes.MATERIALIZED_CLOB;
}
else {
jdbcTypeCode = indicators.isNationalized() ? Types.NVARCHAR : Types.VARCHAR;
}
@ -84,6 +90,18 @@ public class NVarcharJdbcType implements AdjustableJdbcType {
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
protected boolean shouldUseMaterializedLob(JdbcTypeIndicators indicators) {
final Dialect dialect = indicators.getTypeConfiguration()
.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect();
final long length = indicators.getColumnLength();
final long maxLength = indicators.isNationalized() ?
dialect.getMaxNVarcharCapacity() :
dialect.getMaxVarcharCapacity();
return length > maxLength && dialect.useMaterializedLobWhenCapacityExceeded();
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;

View File

@ -12,6 +12,9 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
@ -75,9 +78,23 @@ public class VarbinaryJdbcType implements AdjustableJdbcType {
@Override
public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<?> domainJtd) {
final JdbcTypeRegistry jdbcTypeRegistry = indicators.getTypeConfiguration().getJdbcTypeRegistry();
return indicators.isLob()
? jdbcTypeRegistry.getDescriptor( indicators.resolveJdbcTypeCode( Types.BLOB ) )
: this;
if ( indicators.isLob() ) {
return jdbcTypeRegistry.getDescriptor( indicators.resolveJdbcTypeCode( SqlTypes.BLOB ) );
}
else if ( shouldUseMaterializedLob( indicators ) ) {
return jdbcTypeRegistry.getDescriptor( indicators.resolveJdbcTypeCode( SqlTypes.MATERIALIZED_BLOB ) );
}
return this;
}
protected boolean shouldUseMaterializedLob(JdbcTypeIndicators indicators) {
final Dialect dialect = indicators.getTypeConfiguration()
.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect();
final long length = indicators.getColumnLength();
final long maxLength = dialect.getMaxVarbinaryCapacity();
return length > maxLength && dialect.useMaterializedLobWhenCapacityExceeded();
}
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {

View File

@ -12,6 +12,9 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
@ -65,9 +68,7 @@ public class VarcharJdbcType implements AdjustableJdbcType {
}
@Override
public JdbcType resolveIndicatedType(
JdbcTypeIndicators indicators,
JavaType<?> domainJtd) {
public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<?> domainJtd) {
assert domainJtd != null;
final TypeConfiguration typeConfiguration = indicators.getTypeConfiguration();
@ -77,6 +78,9 @@ public class VarcharJdbcType implements AdjustableJdbcType {
if ( indicators.isLob() ) {
jdbcTypeCode = indicators.isNationalized() ? Types.NCLOB : Types.CLOB;
}
else if ( shouldUseMaterializedLob( indicators ) ) {
jdbcTypeCode = indicators.isNationalized() ? SqlTypes.MATERIALIZED_NCLOB : SqlTypes.MATERIALIZED_CLOB;
}
else {
jdbcTypeCode = indicators.isNationalized() ? Types.NVARCHAR : Types.VARCHAR;
}
@ -84,6 +88,18 @@ public class VarcharJdbcType implements AdjustableJdbcType {
return jdbcTypeRegistry.getDescriptor( indicators.resolveJdbcTypeCode( jdbcTypeCode ) );
}
protected boolean shouldUseMaterializedLob(JdbcTypeIndicators indicators) {
final Dialect dialect = indicators.getTypeConfiguration()
.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect();
final long length = indicators.getColumnLength();
final long maxLength = indicators.isNationalized() ?
dialect.getMaxNVarcharCapacity() :
dialect.getMaxVarcharCapacity();
return length > maxLength && dialect.useMaterializedLobWhenCapacityExceeded();
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;

View File

@ -18,11 +18,12 @@ public class Scale6IntervalSecondDdlType extends DdlTypeImpl {
public Scale6IntervalSecondDdlType(String typeNamePattern, Dialect dialect) {
super( SqlTypes.INTERVAL_SECOND, typeNamePattern, dialect );
}
@Override
public String getTypeName(Long size, Integer precision, Integer scale) {
// The maximum scale for `interval second` is 6 unfortunately, so we have to use numeric by default
// The maximum scale for `interval second` is 6 unfortunately
if ( scale == null || scale > 6 ) {
return DdlTypeImpl.replace( "numeric($p,$s)", size, precision, scale );
throw new IllegalStateException( "Illegal attempt to use interval second type with scale > 6" );
}
return super.getTypeName( size, precision, scale );
}

View File

@ -1,5 +1,10 @@
package org.hibernate.orm.test.length;
import org.hibernate.Length;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.type.SqlTypes;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -12,6 +17,18 @@ import static org.junit.Assert.assertEquals;
public class LengthTest {
@Test
public void testLength(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final BasicValuedMapping mapping = (BasicValuedMapping) scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( WithLongStrings.class )
.findAttributeMapping( "long32" );
if ( dialect.useMaterializedLobWhenCapacityExceeded() && Length.LONG32 > dialect.getMaxVarcharCapacity() ) {
assertEquals( SqlTypes.CLOB, mapping.getJdbcMapping().getJdbcType().getJdbcTypeCode() );
}
else {
assertEquals( SqlTypes.VARCHAR, mapping.getJdbcMapping().getJdbcType().getJdbcTypeCode() );
}
WithLongStrings strings = new WithLongStrings();
strings.longish = "hello world ".repeat(2500);
strings.long16 = "hello world ".repeat(2700);