diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 2a6023a362..09499f3fc9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -67,6 +67,9 @@ import org.hibernate.sql.CaseFragment; import org.hibernate.sql.ForUpdateFragment; import org.hibernate.sql.JoinFragment; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.util.ReflectHelper; import org.hibernate.util.StringHelper; @@ -293,6 +296,67 @@ public abstract class Dialect { typeNames.put( code, name ); } + /** + * Allows the dialect to override a {@link SqlTypeDescriptor}. + *

+ * If sqlTypeDescriptor is a "standard basic" SQL type + * descriptor, then this method uses {@link #getSqlTypeDescriptorOverride} + * to get an optional override based on the SQL code returned by + * {@link SqlTypeDescriptor#getSqlType()}. + *

+ * If this dialect does not provide an override, then this method + * simply returns sqlTypeDescriptor + * + * @param sqlTypeDescriptor The {@link SqlTypeDescriptor} to override + * @return The {@link SqlTypeDescriptor} that should be used for this dialect; + * if there is no override, then sqlTypeDescriptor is returned. + * @throws IllegalArgumentException if sqlTypeDescriptor is null. + * + * @see {@link SqlTypeDescriptor} + * @see {@link #getSqlTypeDescriptorOverride} + * @see {@link StandardBasicTypes#isStandardBasicSqlTypeDescriptor(org.hibernate.type.descriptor.sql.SqlTypeDescriptor)} + */ + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + if ( sqlTypeDescriptor == null ) { + throw new IllegalArgumentException( "sqlTypeDescriptor is null" ); + } + SqlTypeDescriptor overrideBySqlCode = null; + if ( StandardBasicTypes.isStandardBasicSqlTypeDescriptor( sqlTypeDescriptor ) ) { + overrideBySqlCode = getSqlTypeDescriptorOverride( sqlTypeDescriptor.getSqlType() ); + } + return overrideBySqlCode == null ? sqlTypeDescriptor : overrideBySqlCode; + } + + /** + * Returns the {@link SqlTypeDescriptor} that should override the + * "standard basic" SQL type descriptor for values of the specified + * column type, or null, if there is no override. + * + * @param sqlCode A {@link Types} constant indicating the SQL column type + * @return The {@link SqlTypeDescriptor} that should override the + * "standard basic" SQL type descriptor, or null, if there is no override. + * + * @see {@link SqlTypeDescriptor} + * @see {@link StandardBasicTypes#isStandardBasicSqlTypeDescriptor(org.hibernate.type.descriptor.sql.SqlTypeDescriptor)} + */ + protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + SqlTypeDescriptor descriptor; + switch ( sqlCode ) { + case Types.BLOB: { + descriptor = useInputStreamToInsertBlob() ? BlobTypeDescriptor.STREAM_BINDING : null; + break; + } + case Types.CLOB: { + descriptor = useInputStreamToInsertBlob() ? ClobTypeDescriptor.STREAM_BINDING : null; + break; + } + default: { + descriptor = null; + break; + } + } + return descriptor; + } // hibernate type mapping support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 81a3a25e4e..31402deac3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -40,6 +40,9 @@ import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.exception.TemplatedViolatedConstraintNameExtracter; import org.hibernate.exception.ViolatedConstraintNameExtracter; import org.hibernate.id.SequenceGenerator; +import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * An SQL dialect for Postgres @@ -144,6 +147,30 @@ public class PostgreSQLDialect extends Dialect { registerFunction( "str", new SQLFunctionTemplate(Hibernate.STRING, "cast(?1 as varchar)") ); getDefaultProperties().setProperty(Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE); + getDefaultProperties().setProperty( Environment.NON_CONTEXTUAL_LOB_CREATION, "true" ); + } + + /** + * {@inheritDoc} + */ + @Override + public SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + SqlTypeDescriptor descriptor; + switch ( sqlCode ) { + case Types.BLOB: { + descriptor = BlobTypeDescriptor.BLOB_BINDING; + break; + } + case Types.CLOB: { + descriptor = ClobTypeDescriptor.CLOB_BINDING; + break; + } + default: { + descriptor = super.getSqlTypeDescriptorOverride( sqlCode ); + break; + } + } + return descriptor; } public String getAddColumnString() { @@ -364,8 +391,18 @@ public class PostgreSQLDialect extends Dialect { return false; } + @Override public boolean supportsExpectedLobUsagePattern() { - // seems to have spotty LOB suppport + return true; + } + + @Override + public boolean supportsLobValueChangePropogation() { + return false; + } + + @Override + public boolean supportsUnboundedLobLocatorMaterialization() { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java index b23d343d94..51c6b95122 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java @@ -56,6 +56,10 @@ public abstract class AbstractSingleColumnStandardBasicType public LobCreator getLobCreator() { return NonContextualLobCreator.INSTANCE; } + + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return sqlTypeDescriptor; + } }; public final int sqlType() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index f87a891732..cd10dccbc3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -244,13 +244,17 @@ public abstract class AbstractStandardBasicType public LobCreator getLobCreator() { return Hibernate.getLobCreator( session ); } + + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return session.getFactory().getTypeResolver().resolveSqlTypeDescriptor( sqlTypeDescriptor ); + } }; return nullSafeGet( rs, name, options ); } protected final T nullSafeGet(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return sqlTypeDescriptor.getExtractor( javaTypeDescriptor ).extract( rs, name, options ); + return resolveSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( rs, name, options ); } public Object get(ResultSet rs, String name, SessionImplementor session) throws HibernateException, SQLException { @@ -272,6 +276,10 @@ public abstract class AbstractStandardBasicType public LobCreator getLobCreator() { return Hibernate.getLobCreator( session ); } + + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return session.getFactory().getTypeResolver().resolveSqlTypeDescriptor( sqlTypeDescriptor ); + } }; nullSafeSet( st, value, index, options ); @@ -279,7 +287,11 @@ public abstract class AbstractStandardBasicType @SuppressWarnings({ "unchecked" }) protected final void nullSafeSet(PreparedStatement st, Object value, int index, WrapperOptions options) throws SQLException { - sqlTypeDescriptor.getBinder( javaTypeDescriptor ).bind( st, (T) value, index, options ); + resolveSqlTypeDescriptor( options ).getBinder( javaTypeDescriptor ).bind( st, ( T ) value, index, options ); + } + + private SqlTypeDescriptor resolveSqlTypeDescriptor(WrapperOptions options) { + return options.resolveSqlTypeDescriptor( sqlTypeDescriptor ); } public void set(PreparedStatement st, T value, int index, SessionImplementor session) throws HibernateException, SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/type/BlobType.java b/hibernate-core/src/main/java/org/hibernate/type/BlobType.java index 86f9ad6012..30f37d425e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BlobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BlobType.java @@ -37,7 +37,7 @@ public class BlobType extends AbstractSingleColumnStandardBasicType { public static final BlobType INSTANCE = new BlobType(); public BlobType() { - super( org.hibernate.type.descriptor.sql.BlobTypeDescriptor.INSTANCE, BlobTypeDescriptor.INSTANCE ); + super( org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT, BlobTypeDescriptor.INSTANCE ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayClobType.java b/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayClobType.java index 649135dd3e..c772bc4b35 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayClobType.java @@ -38,7 +38,7 @@ public class CharacterArrayClobType extends AbstractSingleColumnStandardBasicTyp public static final CharacterArrayClobType INSTANCE = new CharacterArrayClobType(); public CharacterArrayClobType() { - super( ClobTypeDescriptor.INSTANCE, CharacterArrayTypeDescriptor.INSTANCE ); + super( ClobTypeDescriptor.DEFAULT, CharacterArrayTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java index d3bbdb0d2e..4bd706a8c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java @@ -37,7 +37,7 @@ public class ClobType extends AbstractSingleColumnStandardBasicType { public static final ClobType INSTANCE = new ClobType(); public ClobType() { - super( org.hibernate.type.descriptor.sql.ClobTypeDescriptor.INSTANCE, ClobTypeDescriptor.INSTANCE ); + super( org.hibernate.type.descriptor.sql.ClobTypeDescriptor.DEFAULT, ClobTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/MaterializedBlobType.java b/hibernate-core/src/main/java/org/hibernate/type/MaterializedBlobType.java index 980dab9737..ff13815f69 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/MaterializedBlobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/MaterializedBlobType.java @@ -38,7 +38,7 @@ public class MaterializedBlobType extends AbstractSingleColumnStandardBasicType< public static final MaterializedBlobType INSTANCE = new MaterializedBlobType(); public MaterializedBlobType() { - super( BlobTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE ); + super( BlobTypeDescriptor.DEFAULT, PrimitiveByteArrayTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/MaterializedClobType.java b/hibernate-core/src/main/java/org/hibernate/type/MaterializedClobType.java index 9dbce2e371..7621e49ad9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/MaterializedClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/MaterializedClobType.java @@ -37,7 +37,7 @@ public class MaterializedClobType extends AbstractSingleColumnStandardBasicType< public static final MaterializedClobType INSTANCE = new MaterializedClobType(); public MaterializedClobType() { - super( ClobTypeDescriptor.INSTANCE, StringTypeDescriptor.INSTANCE ); + super( ClobTypeDescriptor.DEFAULT, StringTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayClobType.java b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayClobType.java index d21bb1e498..3c09a7c8f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayClobType.java @@ -35,7 +35,7 @@ public class PrimitiveCharacterArrayClobType extends AbstractSingleColumnStandar public static final CharacterArrayClobType INSTANCE = new CharacterArrayClobType(); public PrimitiveCharacterArrayClobType() { - super( ClobTypeDescriptor.INSTANCE, PrimitiveCharacterArrayTypeDescriptor.INSTANCE ); + super( ClobTypeDescriptor.DEFAULT, PrimitiveCharacterArrayTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java index 5933ac517b..df54e0a18e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java @@ -23,6 +23,11 @@ */ package org.hibernate.type; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + /** * Centralizes access to the standard set of basic {@link Type types}. *

@@ -37,108 +42,111 @@ package org.hibernate.type; * @author Steve Ebersole */ public class StandardBasicTypes { + + private static final Set sqlTypeDescriptors = new HashSet(); + /** * The standard Hibernate type for mapping {@link Boolean} to JDBC {@link java.sql.Types#BIT BIT}. * * @see BooleanType */ - public static final BooleanType BOOLEAN = BooleanType.INSTANCE; + public static final BooleanType BOOLEAN = register( BooleanType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Boolean} to JDBC {@link java.sql.Types#INTEGER INTEGER}. * * @see NumericBooleanType */ - public static final NumericBooleanType NUMERIC_BOOLEAN = NumericBooleanType.INSTANCE; + public static final NumericBooleanType NUMERIC_BOOLEAN = register( NumericBooleanType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Boolean} to JDBC {@link java.sql.Types#CHAR CHAR(1)} (using 'T'/'F'). * * @see TrueFalseType */ - public static final TrueFalseType TRUE_FALSE = TrueFalseType.INSTANCE; + public static final TrueFalseType TRUE_FALSE = register( TrueFalseType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Boolean} to JDBC {@link java.sql.Types#CHAR CHAR(1)} (using 'Y'/'N'). * * @see YesNoType */ - public static final YesNoType YES_NO = YesNoType.INSTANCE; + public static final YesNoType YES_NO = register( YesNoType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Byte} to JDBC {@link java.sql.Types#TINYINT TINYINT}. */ - public static final ByteType BYTE = ByteType.INSTANCE; + public static final ByteType BYTE = register( ByteType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Short} to JDBC {@link java.sql.Types#SMALLINT SMALLINT}. * * @see ShortType */ - public static final ShortType SHORT = ShortType.INSTANCE; + public static final ShortType SHORT = register( ShortType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Integer} to JDBC {@link java.sql.Types#INTEGER INTEGER}. * * @see IntegerType */ - public static final IntegerType INTEGER = IntegerType.INSTANCE; + public static final IntegerType INTEGER = register( IntegerType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Long} to JDBC {@link java.sql.Types#BIGINT BIGINT}. * * @see LongType */ - public static final LongType LONG = LongType.INSTANCE; + public static final LongType LONG = register( LongType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Float} to JDBC {@link java.sql.Types#FLOAT FLOAT}. * * @see FloatType */ - public static final FloatType FLOAT = FloatType.INSTANCE; + public static final FloatType FLOAT = register( FloatType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Double} to JDBC {@link java.sql.Types#DOUBLE DOUBLE}. * * @see DoubleType */ - public static final DoubleType DOUBLE = DoubleType.INSTANCE; + public static final DoubleType DOUBLE = register( DoubleType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.math.BigInteger} to JDBC {@link java.sql.Types#NUMERIC NUMERIC}. * * @see BigIntegerType */ - public static final BigIntegerType BIG_INTEGER = BigIntegerType.INSTANCE; + public static final BigIntegerType BIG_INTEGER = register( BigIntegerType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.math.BigDecimal} to JDBC {@link java.sql.Types#NUMERIC NUMERIC}. * * @see BigDecimalType */ - public static final BigDecimalType BIG_DECIMAL = BigDecimalType.INSTANCE; + public static final BigDecimalType BIG_DECIMAL = register( BigDecimalType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Character} to JDBC {@link java.sql.Types#CHAR CHAR(1)}. * * @see CharacterType */ - public static final CharacterType CHARACTER = CharacterType.INSTANCE; + public static final CharacterType CHARACTER = register( CharacterType.INSTANCE ); /** * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see StringType */ - public static final StringType STRING = StringType.INSTANCE; + public static final StringType STRING = register( StringType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.net.URL} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see UrlType */ - public static final UrlType URL = UrlType.INSTANCE; + public static final UrlType URL = register( UrlType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Date} ({@link java.sql.Time}) to JDBC @@ -146,7 +154,7 @@ public class StandardBasicTypes { * * @see TimeType */ - public static final TimeType TIME = TimeType.INSTANCE; + public static final TimeType TIME = register( TimeType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Date} ({@link java.sql.Date}) to JDBC @@ -154,7 +162,7 @@ public class StandardBasicTypes { * * @see TimeType */ - public static final DateType DATE = DateType.INSTANCE; + public static final DateType DATE = register( DateType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Date} ({@link java.sql.Timestamp}) to JDBC @@ -162,7 +170,7 @@ public class StandardBasicTypes { * * @see TimeType */ - public static final TimestampType TIMESTAMP = TimestampType.INSTANCE; + public static final TimestampType TIMESTAMP = register( TimestampType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Calendar} to JDBC @@ -170,7 +178,7 @@ public class StandardBasicTypes { * * @see CalendarType */ - public static final CalendarType CALENDAR = CalendarType.INSTANCE; + public static final CalendarType CALENDAR = register( CalendarType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Calendar} to JDBC @@ -178,63 +186,63 @@ public class StandardBasicTypes { * * @see CalendarDateType */ - public static final CalendarDateType CALENDAR_DATE = CalendarDateType.INSTANCE; + public static final CalendarDateType CALENDAR_DATE = register( CalendarDateType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Class} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see ClassType */ - public static final ClassType CLASS = ClassType.INSTANCE; + public static final ClassType CLASS = register( ClassType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Locale} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see LocaleType */ - public static final LocaleType LOCALE = LocaleType.INSTANCE; + public static final LocaleType LOCALE = register( LocaleType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.Currency} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see CurrencyType */ - public static final CurrencyType CURRENCY = CurrencyType.INSTANCE; + public static final CurrencyType CURRENCY = register( CurrencyType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.TimeZone} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see TimeZoneType */ - public static final TimeZoneType TIMEZONE = TimeZoneType.INSTANCE; + public static final TimeZoneType TIMEZONE = register( TimeZoneType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.UUID} to JDBC {@link java.sql.Types#BINARY BINARY}. * * @see UUIDBinaryType */ - public static final UUIDBinaryType UUID_BINARY = UUIDBinaryType.INSTANCE; + public static final UUIDBinaryType UUID_BINARY = register( UUIDBinaryType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.util.UUID} to JDBC {@link java.sql.Types#CHAR CHAR}. * * @see UUIDCharType */ - public static final UUIDCharType UUID_CHAR = UUIDCharType.INSTANCE; + public static final UUIDCharType UUID_CHAR = register( UUIDCharType.INSTANCE ); /** * The standard Hibernate type for mapping {@code byte[]} to JDBC {@link java.sql.Types#VARBINARY VARBINARY}. * * @see BinaryType */ - public static final BinaryType BINARY = BinaryType.INSTANCE; + public static final BinaryType BINARY = register( BinaryType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Byte Byte[]} to JDBC {@link java.sql.Types#VARBINARY VARBINARY}. * * @see WrapperBinaryType */ - public static final WrapperBinaryType WRAPPER_BINARY = WrapperBinaryType.INSTANCE; + public static final WrapperBinaryType WRAPPER_BINARY = register( WrapperBinaryType.INSTANCE ); /** * The standard Hibernate type for mapping {@code byte[]} to JDBC {@link java.sql.Types#LONGVARBINARY LONGVARBINARY}. @@ -242,7 +250,7 @@ public class StandardBasicTypes { * @see ImageType * @see #MATERIALIZED_BLOB */ - public static final ImageType IMAGE = ImageType.INSTANCE; + public static final ImageType IMAGE = register( ImageType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.sql.Blob} to JDBC {@link java.sql.Types#BLOB BLOB}. @@ -250,7 +258,7 @@ public class StandardBasicTypes { * @see BlobType * @see #MATERIALIZED_BLOB */ - public static final BlobType BLOB = BlobType.INSTANCE; + public static final BlobType BLOB = register( BlobType.INSTANCE ); /** * The standard Hibernate type for mapping {@code byte[]} to JDBC {@link java.sql.Types#BLOB BLOB}. @@ -259,14 +267,14 @@ public class StandardBasicTypes { * @see #MATERIALIZED_BLOB * @see #IMAGE */ - public static final MaterializedBlobType MATERIALIZED_BLOB = MaterializedBlobType.INSTANCE; + public static final MaterializedBlobType MATERIALIZED_BLOB = register( MaterializedBlobType.INSTANCE ); /** * The standard Hibernate type for mapping {@code char[]} to JDBC {@link java.sql.Types#VARCHAR VARCHAR}. * * @see CharArrayType */ - public static final CharArrayType CHAR_ARRAY = CharArrayType.INSTANCE; + public static final CharArrayType CHAR_ARRAY = register( CharArrayType.INSTANCE ); /** * The standard Hibernate type for mapping {@link Character Character[]} to JDBC @@ -274,7 +282,7 @@ public class StandardBasicTypes { * * @see CharacterArrayType */ - public static final CharacterArrayType CHARACTER_ARRAY = CharacterArrayType.INSTANCE; + public static final CharacterArrayType CHARACTER_ARRAY = register( CharacterArrayType.INSTANCE ); /** * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#LONGVARCHAR LONGVARCHAR}. @@ -283,7 +291,7 @@ public class StandardBasicTypes { * * @see TextType */ - public static final TextType TEXT = TextType.INSTANCE; + public static final TextType TEXT = register( TextType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.sql.Clob} to JDBC {@link java.sql.Types#CLOB CLOB}. @@ -291,7 +299,7 @@ public class StandardBasicTypes { * @see ClobType * @see #MATERIALIZED_CLOB */ - public static final ClobType CLOB = ClobType.INSTANCE; + public static final ClobType CLOB = register( ClobType.INSTANCE ); /** * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#CLOB CLOB}. @@ -300,7 +308,7 @@ public class StandardBasicTypes { * @see #MATERIALIZED_CLOB * @see #TEXT */ - public static final MaterializedClobType MATERIALIZED_CLOB = MaterializedClobType.INSTANCE; + public static final MaterializedClobType MATERIALIZED_CLOB = register( MaterializedClobType.INSTANCE ); /** * The standard Hibernate type for mapping {@link java.io.Serializable} to JDBC {@link java.sql.Types#VARBINARY VARBINARY}. @@ -309,5 +317,14 @@ public class StandardBasicTypes { * * @see SerializableType */ - public static final SerializableType SERIALIZABLE = SerializableType.INSTANCE; + public static final SerializableType SERIALIZABLE = register( SerializableType.INSTANCE ); + + private static T register(T type) { + sqlTypeDescriptors.add( type.getSqlTypeDescriptor() ); + return type; + } + + public static final boolean isStandardBasicSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return sqlTypeDescriptors.contains( sqlTypeDescriptor ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 5a08ec00f2..bb4db8e200 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -86,6 +86,10 @@ public final class TypeFactory implements Serializable { typeScope.injectSessionFactory( factory ); } + public SessionFactoryImplementor resolveSessionFactory() { + return typeScope.resolveFactory(); + } + public Type byClass(Class clazz, Properties parameters) { if ( Type.class.isAssignableFrom( clazz ) ) { return type( (Class) clazz, parameters ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java index 83231165a9..3006a643e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeResolver.java @@ -24,11 +24,17 @@ package org.hibernate.type; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.hibernate.MappingException; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserType; import org.hibernate.util.ReflectHelper; @@ -39,21 +45,31 @@ import org.hibernate.util.ReflectHelper; * @author Steve Ebersole */ public class TypeResolver implements Serializable { + private static final Logger log = LoggerFactory.getLogger( TypeResolver.class ); + private final BasicTypeRegistry basicTypeRegistry; private final TypeFactory typeFactory; + private final Map resolvedSqlTypeDescriptors; public TypeResolver() { - this( new BasicTypeRegistry(), new TypeFactory() ); + this( new BasicTypeRegistry(), new TypeFactory(), null ); } - public TypeResolver(BasicTypeRegistry basicTypeRegistry, TypeFactory typeFactory) { + public TypeResolver(BasicTypeRegistry basicTypeRegistry, + TypeFactory typeFactory, + Map resolvedSqlTypeDescriptors) { this.basicTypeRegistry = basicTypeRegistry; this.typeFactory = typeFactory; + this.resolvedSqlTypeDescriptors = resolvedSqlTypeDescriptors; } public TypeResolver scope(SessionFactoryImplementor factory) { typeFactory.injectSessionFactory( factory ); - return new TypeResolver( basicTypeRegistry.shallowCopy(), typeFactory ); + return new TypeResolver( + basicTypeRegistry.shallowCopy(), + typeFactory, + new HashMap( 25 ) + ); } public void registerTypeOverride(BasicType type) { @@ -135,4 +151,39 @@ public class TypeResolver implements Serializable { return null; } + + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + if ( resolvedSqlTypeDescriptors == null ) { + throw new IllegalStateException( "cannot resolve a SqlTypeDescriptor until the TypeResolver is scoped." ); + } + SqlTypeDescriptor resolvedDescriptor = resolvedSqlTypeDescriptors.get( sqlTypeDescriptor ); + if ( resolvedDescriptor == null ) { + resolvedDescriptor = + typeFactory.resolveSessionFactory().getDialect().resolveSqlTypeDescriptor( sqlTypeDescriptor ); + if ( resolvedDescriptor == null ) { + throw new IllegalStateException( "dialect returned a resolved SqlTypeDescriptor that was null." ); + } + if ( sqlTypeDescriptor != resolvedDescriptor ) { + log.info( + "Adding override for {}: {}", + new String[] { + sqlTypeDescriptor.getClass().getName(), + resolvedDescriptor.getClass().getName(), + } + ); + if ( sqlTypeDescriptor.getSqlType() != resolvedDescriptor.getSqlType() ) { + log.warn( "Resolved SqlTypeDescriptor is for a different SQL code. {} has sqlCode={}; type override {} has sqlCode={}", + new String[] { + sqlTypeDescriptor.getClass().getName(), + String.valueOf( sqlTypeDescriptor.getSqlType() ), + resolvedDescriptor.getClass().getName(), + String.valueOf( resolvedDescriptor.getSqlType() ), + } + ); + } + } + resolvedSqlTypeDescriptors.put( sqlTypeDescriptor, resolvedDescriptor ); + } + return resolvedDescriptor; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/WrappedMaterializedBlobType.java b/hibernate-core/src/main/java/org/hibernate/type/WrappedMaterializedBlobType.java index 96f340f4d1..975ffd5a43 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/WrappedMaterializedBlobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/WrappedMaterializedBlobType.java @@ -36,7 +36,7 @@ public class WrappedMaterializedBlobType extends AbstractSingleColumnStandardBas public static final WrappedMaterializedBlobType INSTANCE = new WrappedMaterializedBlobType(); public WrappedMaterializedBlobType() { - super( BlobTypeDescriptor.INSTANCE, ByteArrayTypeDescriptor.INSTANCE ); + super( BlobTypeDescriptor.DEFAULT, ByteArrayTypeDescriptor.INSTANCE ); } public String getName() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java index 339e8eb8c8..6cc8949804 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java @@ -23,7 +23,10 @@ */ package org.hibernate.type.descriptor; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.LobCreator; +import org.hibernate.type.TypeResolver; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * TODO : javadoc @@ -33,4 +36,5 @@ import org.hibernate.engine.jdbc.LobCreator; public interface WrapperOptions { public boolean useStreamForLobBinding(); public LobCreator getLobCreator(); + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobTypeDescriptor.java index f834c11934..d7b104657f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobTypeDescriptor.java @@ -31,6 +31,7 @@ import java.util.Comparator; import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.BlobProxy; import org.hibernate.engine.jdbc.WrappedBlob; +import org.hibernate.type.descriptor.BinaryStream; import org.hibernate.type.descriptor.WrapperOptions; /** @@ -107,7 +108,7 @@ public class BlobTypeDescriptor extends AbstractTypeDescriptor { @SuppressWarnings({ "unchecked" }) public X unwrap(Blob value, Class type, WrapperOptions options) { - if ( !Blob.class.isAssignableFrom( type ) ) { + if ( ! ( Blob.class.isAssignableFrom( type ) || BinaryStream.class.isAssignableFrom( type ) ) ) { throw unknownUnwrap( type ); } @@ -115,6 +116,15 @@ public class BlobTypeDescriptor extends AbstractTypeDescriptor { return null; } + if ( BinaryStream.class.isAssignableFrom( type ) ) { + try { + return (X) new BinaryStreamImpl( DataHelper.extractBytes( value.getBinaryStream() ) ); + } + catch ( SQLException e ) { + throw new HibernateException( "Unable to access blob stream", e ); + } + } + final Blob blob = WrappedBlob.class.isInstance( value ) ? ( (WrappedBlob) value ).getWrappedBlob() : value; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java index ec10483c49..bbe51382d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.java; +import java.io.Reader; import java.io.Serializable; import java.sql.Clob; import java.sql.SQLException; @@ -31,6 +32,7 @@ import java.util.Comparator; import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.ClobProxy; import org.hibernate.engine.jdbc.WrappedClob; +import org.hibernate.type.descriptor.CharacterStream; import org.hibernate.type.descriptor.WrapperOptions; /** @@ -98,8 +100,8 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor { } @SuppressWarnings({ "unchecked" }) - public X unwrap(Clob value, Class type, WrapperOptions options) { - if ( !Clob.class.isAssignableFrom( type ) ) { + public X unwrap(final Clob value, Class type, WrapperOptions options) { + if ( ! ( Clob.class.isAssignableFrom( type ) || CharacterStream.class.isAssignableFrom( type ) ) ) { throw unknownUnwrap( type ); } @@ -107,6 +109,15 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor { return null; } + if ( CharacterStream.class.isAssignableFrom( type ) ) { + try { + return (X) new CharacterStreamImpl( DataHelper.extractString( value.getCharacterStream() ) ); + } + catch ( SQLException e ) { + throw new HibernateException( "Unable to access lob stream", e ); + } + } + final Clob clob = WrappedClob.class.isInstance( value ) ? ( (WrappedClob) value ).getWrappedClob() : value; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java index c746316bfd..eec639d4a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java @@ -23,7 +23,6 @@ */ package org.hibernate.type.descriptor.sql; -import java.io.InputStream; import java.sql.Blob; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -41,32 +40,72 @@ import org.hibernate.type.descriptor.WrapperOptions; * * @author Steve Ebersole */ -public class BlobTypeDescriptor implements SqlTypeDescriptor { - public static final BlobTypeDescriptor INSTANCE = new BlobTypeDescriptor(); +public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { - public int getSqlType() { - return Types.BLOB; - } + private BlobTypeDescriptor() {} - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicBinder( javaTypeDescriptor, this ) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - if ( options.useStreamForLobBinding() ) { - final BinaryStream binaryStream = javaTypeDescriptor.unwrap( value, BinaryStream.class, options ); - st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() ); + public static final BlobTypeDescriptor DEFAULT = + new BlobTypeDescriptor() { + public BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + if ( options.useStreamForLobBinding() ) { + STREAM_BINDING.getBlobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + else if ( byte[].class.isInstance( value ) ) { + // performance shortcut for binding BLOB data in byte[] format + PRIMITIVE_ARRAY_BINDING.getBlobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + else { + BLOB_BINDING.getBlobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + } + }; } - else if ( byte[].class.isInstance( value ) ) { - // performance shortcut for binding BLOB data in byte[] format - final byte[] bytes = (byte[]) value; - st.setBytes( index, bytes ); + }; + + public static final BlobTypeDescriptor PRIMITIVE_ARRAY_BINDING = + new BlobTypeDescriptor() { + public BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + public void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setBytes( index, javaTypeDescriptor.unwrap( value, byte[].class, options ) ); + } + }; } - else { - st.setBlob( index, javaTypeDescriptor.unwrap( value, Blob.class, options ) ); + }; + + public static final BlobTypeDescriptor BLOB_BINDING = + new BlobTypeDescriptor() { + public BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setBlob( index, javaTypeDescriptor.unwrap( value, Blob.class, options ) ); + } + }; } - } - }; - } + }; + + public static final BlobTypeDescriptor STREAM_BINDING = + new BlobTypeDescriptor() { + public BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final BinaryStream binaryStream = javaTypeDescriptor.unwrap( value, BinaryStream.class, options ); + st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() ); + } + }; + } + }; + + protected abstract BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor); public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @@ -76,4 +115,12 @@ public class BlobTypeDescriptor implements SqlTypeDescriptor { } }; } + + public int getSqlType() { + return Types.BLOB; + } + + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return getBlobBinder( javaTypeDescriptor ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java index 218e9e9a99..a0913e97a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java @@ -41,28 +41,62 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * * @author Steve Ebersole */ -public class ClobTypeDescriptor implements SqlTypeDescriptor { - public static final ClobTypeDescriptor INSTANCE = new ClobTypeDescriptor(); +public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { + + public static final ClobTypeDescriptor DEFAULT = + new ClobTypeDescriptor() { + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + if ( options.useStreamForLobBinding() ) { + STREAM_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + else { + CLOB_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + } + }; + } + }; + + public static final ClobTypeDescriptor CLOB_BINDING = + new ClobTypeDescriptor() { + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setClob( index, javaTypeDescriptor.unwrap( value, Clob.class, options ) ); + } + }; + } + }; + + public static final ClobTypeDescriptor STREAM_BINDING = + new ClobTypeDescriptor() { + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); + st.setCharacterStream( index, characterStream.getReader(), characterStream.getLength() ); + } + }; + } + }; + + protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); + + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return getClobBinder( javaTypeDescriptor ); + } public int getSqlType() { return Types.CLOB; } - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicBinder( javaTypeDescriptor, this ) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - if ( options.useStreamForLobBinding() ) { - final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); - st.setCharacterStream( index, characterStream.getReader(), characterStream.getLength() ); - } - else { - st.setClob( index, javaTypeDescriptor.unwrap( value, Clob.class, options ) ); - } - } - }; - } - public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/MaterializedBlobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/MaterializedBlobTest.java index dcecebd8e0..de19373961 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/MaterializedBlobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/MaterializedBlobTest.java @@ -28,7 +28,7 @@ import java.util.Arrays; import org.hibernate.Session; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; +import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.test.annotations.TestCase; import org.hibernate.testing.junit.DialectChecks; import org.hibernate.testing.junit.RequiresDialectFeature; diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.hbm.xml new file mode 100644 index 0000000000..84386ea601 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.hbm.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.java new file mode 100644 index 0000000000..e1f1f3e884 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/Entity.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.typeoverride; + +/** + * @author Gail Badner + */ +public class Entity { + private long id; + private String name; + + public Entity() { + } + + public Entity(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverridePrefixedVarcharSqlTypeDesc.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverridePrefixedVarcharSqlTypeDesc.java new file mode 100644 index 0000000000..b6ec851395 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverridePrefixedVarcharSqlTypeDesc.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.typeoverride; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; + +/** + * + * @author Gail Badner + */ +public class H2DialectOverridePrefixedVarcharSqlTypeDesc extends H2Dialect { + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return sqlTypeDescriptor == StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ? + VarcharTypeDescriptor.INSTANCE : + super.resolveSqlTypeDescriptor( sqlTypeDescriptor ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverrideVarcharSqlCode.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverrideVarcharSqlCode.java new file mode 100644 index 0000000000..7e2be2b90d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/H2DialectOverrideVarcharSqlCode.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.typeoverride; + +import java.sql.Types; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +/** + * + * @author Gail Badner + */ +public class H2DialectOverrideVarcharSqlCode extends H2Dialect { + public SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + return sqlCode == Types.VARCHAR ? + StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() : + super.getSqlTypeDescriptorOverride( sqlCode ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java new file mode 100644 index 0000000000..e00fb49984 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.typeoverride; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.AssertionFailure; +import org.hibernate.dialect.Dialect; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.DiscriminatorType; +import org.hibernate.type.StringType; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.BasicBinder; +import org.hibernate.type.descriptor.sql.BasicExtractor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; + +/** + * + * @author Gail Badner + */ +public class StoredPrefixedStringType + extends AbstractSingleColumnStandardBasicType + implements DiscriminatorType { + public static final String PREFIX = "PRE:"; + private static final SqlTypeDescriptor PREFIXED_VARCHAR_TYPE_DESCRIPTOR = + new VarcharTypeDescriptor() { + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + String stringValue = javaTypeDescriptor.unwrap( value, String.class, options ); + st.setString( index, PREFIX + stringValue ); + } + }; + } + + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + String stringValue = rs.getString( name ); + if ( ! stringValue.startsWith( PREFIX ) ) { + throw new AssertionFailure( "Value read from resultset does not have prefix." ); + } + return javaTypeDescriptor.wrap( stringValue.substring( PREFIX.length() ), options ); + } + }; + } + }; + + + public static final StoredPrefixedStringType INSTANCE = new StoredPrefixedStringType(); + + public StoredPrefixedStringType() { + super( PREFIXED_VARCHAR_TYPE_DESCRIPTOR, StringType.INSTANCE.getJavaTypeDescriptor() ); + } + + public String getName() { + return StringType.INSTANCE.getName(); + } + + @Override + protected boolean registerUnderJavaType() { + return true; + } + + public String objectToSQLString(String value, Dialect dialect) throws Exception { + return StringType.INSTANCE.objectToSQLString( value, dialect ); + } + + public String stringToObject(String xml) throws Exception { + return StringType.INSTANCE.stringToObject( xml ); + } + + public String toString(String value) { + return StringType.INSTANCE.toString( value ); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/TypeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/TypeOverrideTest.java new file mode 100644 index 0000000000..f15f158df6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/TypeOverrideTest.java @@ -0,0 +1,208 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * 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 GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.typeoverride; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.testing.junit.functional.FunctionalTestCase; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.IntegerTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; + +/** + * + * @author Gail Badner + */ +public class TypeOverrideTest extends FunctionalTestCase { + + public TypeOverrideTest(String string) { + super( string ); + } + + public String[] getMappings() { + return new String[] { "typeoverride/Entity.hbm.xml" }; + } + + @Override + public void configure(Configuration cfg) { + cfg.registerTypeOverride( StoredPrefixedStringType.INSTANCE ); + } + + public void testStandardBasicSqlTypeDescriptor() { + // no override + assertTrue( StandardBasicTypes.isStandardBasicSqlTypeDescriptor( IntegerTypeDescriptor.INSTANCE ) ); + assertSame( IntegerTypeDescriptor.INSTANCE, getResolvedSqlTypeDescriptor( IntegerTypeDescriptor.INSTANCE ) ); + + // override depends on Dialect.useInputStreamToInsertBlob(); + // Postgresql explicitly overrides BlobTypeDescriptor.DEFAULT + assertTrue( StandardBasicTypes.isStandardBasicSqlTypeDescriptor( BlobTypeDescriptor.DEFAULT ) ); + if ( getDialect().useInputStreamToInsertBlob() ) { + assertSame( + BlobTypeDescriptor.STREAM_BINDING, + getDialect().resolveSqlTypeDescriptor( BlobTypeDescriptor.DEFAULT ) + ); + } + else if ( PostgreSQLDialect.class.isInstance( getDialect() ) ) { + assertSame( + BlobTypeDescriptor.BLOB_BINDING, + getDialect().resolveSqlTypeDescriptor( BlobTypeDescriptor.DEFAULT ) + ); + } + else { + assertSame( + BlobTypeDescriptor.DEFAULT, + getDialect().resolveSqlTypeDescriptor( BlobTypeDescriptor.DEFAULT ) + ); + } + } + + public void testNonStandardSqlTypeDescriptor() { + // no override + SqlTypeDescriptor sqlTypeDescriptor = new IntegerTypeDescriptor(); + assertFalse( StandardBasicTypes.isStandardBasicSqlTypeDescriptor( sqlTypeDescriptor ) ); + assertSame( sqlTypeDescriptor, getResolvedSqlTypeDescriptor( sqlTypeDescriptor ) ); + + // no override; (ClobTypeDescriptor.DEFAULT is overridden + // if Dialect.useInputStreamToInsertBlob() is true) + assertFalse( StandardBasicTypes.isStandardBasicSqlTypeDescriptor( ClobTypeDescriptor.CLOB_BINDING ) ); + assertSame( ClobTypeDescriptor.CLOB_BINDING, getResolvedSqlTypeDescriptor( ClobTypeDescriptor.CLOB_BINDING ) ); + } + + public void testDialectWithNonStandardSqlTypeDescriptor() { + assertNotSame( VarcharTypeDescriptor.INSTANCE, StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ); + if ( H2DialectOverridePrefixedVarcharSqlTypeDesc.class.isInstance( getDialect() ) ) { + // TODO: dialect is currently a global; how can this be tested in the testsuite? + assertSame( + VarcharTypeDescriptor.INSTANCE, + getResolvedSqlTypeDescriptor( StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ) + ); + } + else { + assertSame( + StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor(), + getResolvedSqlTypeDescriptor( StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ) + ); + } + + if ( H2DialectOverrideVarcharSqlCode.class.isInstance( getDialect() ) ) { + // TODO: dialect is currently a global; how can this be tested in the testsuite? + assertSame( + StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor(), + getResolvedSqlTypeDescriptor( VarcharTypeDescriptor.INSTANCE ) + ); + } + else { + assertSame( + VarcharTypeDescriptor.INSTANCE, + getResolvedSqlTypeDescriptor( VarcharTypeDescriptor.INSTANCE ) + ); + } + } + + private SqlTypeDescriptor getResolvedSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return ( ( SessionFactoryImplementor ) getSessions() ) + .getTypeResolver() + .resolveSqlTypeDescriptor( sqlTypeDescriptor ); + } + + public void testInsert() { + Session s = openSession(); + s.getTransaction().begin(); + Entity e = new Entity( "name" ); + s.save( e ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + e = ( Entity ) s.get( Entity.class, e.getId() ); + assertFalse( e.getName().startsWith( StoredPrefixedStringType.PREFIX ) ); + assertEquals( "name", e.getName() ); + s.delete( e ); + s.getTransaction().commit(); + s.close(); + } + + public void testRegisteredFunction() { + Session s = openSession(); + s.getTransaction().begin(); + Entity e = new Entity( "name " ); + s.save( e ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + e = ( Entity ) s.get( Entity.class, e.getId() ); + assertFalse( e.getName().startsWith( StoredPrefixedStringType.PREFIX ) ); + assertEquals( "name ", e.getName() ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.getTransaction().begin(); + String trimmedName = ( String ) s.createQuery( "select trim( TRAILING from e.name ) from Entity e" ).uniqueResult(); + // trim(...) is a "standard" DB function returning VarcharTypeDescriptor.INSTANCE, + // so the prefix will not be removed unless + // 1) getDialect().getSqlTypeDescriptorOverride( VarcharTypeDescriptor.INSTANCE ) + // returns StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() + // (H2DialectOverrideVarcharSqlCode does this) + // or 2) getDialect().getSqlTypeDescriptorOverride( StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ) + // returns VarcharTypeDescriptor.INSTANCE + // (H2DialectOverridePrefixedVarcharSqlTypeDesc does this) + // TODO: dialect is currently a global; how can this be tested in the testsuite? + assertNotSame( VarcharTypeDescriptor.INSTANCE, StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ); + if ( getDialect().resolveSqlTypeDescriptor( VarcharTypeDescriptor.INSTANCE ) == + StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() || + getDialect().resolveSqlTypeDescriptor( StoredPrefixedStringType.INSTANCE.getSqlTypeDescriptor() ) == + VarcharTypeDescriptor.INSTANCE ) { + assertFalse( trimmedName.startsWith( StoredPrefixedStringType.PREFIX ) ); + assertEquals( "name", trimmedName ); + } + else { + assertSame( + VarcharTypeDescriptor.INSTANCE, + ( ( SessionFactoryImplementor ) getSessions() ) + .getTypeResolver() + .resolveSqlTypeDescriptor( VarcharTypeDescriptor.INSTANCE ) + ); + assertTrue( trimmedName.startsWith( StoredPrefixedStringType.PREFIX ) ); + assertEquals( StoredPrefixedStringType.PREFIX + "name", trimmedName ); + } + s.delete( e ); + s.getTransaction().commit(); + s.close(); + } +} + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/type/descriptor/sql/StringValueMappingTest.java b/hibernate-core/src/test/java/org/hibernate/type/descriptor/sql/StringValueMappingTest.java index 7e870dc8d8..3fd78a2f03 100644 --- a/hibernate-core/src/test/java/org/hibernate/type/descriptor/sql/StringValueMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/type/descriptor/sql/StringValueMappingTest.java @@ -36,8 +36,6 @@ import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.StringTypeDescriptor; -import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; -import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** * TODO : javadoc @@ -48,7 +46,7 @@ public class StringValueMappingTest extends TestCase { private final StringTypeDescriptor stringJavaDescriptor = new StringTypeDescriptor(); private final VarcharTypeDescriptor varcharSqlDescriptor = new VarcharTypeDescriptor(); - private final ClobTypeDescriptor clobSqlDescriptor = new ClobTypeDescriptor(); + private final ClobTypeDescriptor clobSqlDescriptor = ClobTypeDescriptor.DEFAULT; private final WrapperOptions wrapperOptions = new WrapperOptions() { public boolean useStreamForLobBinding() { @@ -58,6 +56,10 @@ public class StringValueMappingTest extends TestCase { public LobCreator getLobCreator() { return NonContextualLobCreator.INSTANCE; } + + public SqlTypeDescriptor resolveSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + return sqlTypeDescriptor; + } }; public static final String COLUMN_NAME = "n/a";