From 2f8f04747b5a4555836d5b7e53da204c2fdaf022 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 27 Jul 2020 16:44:22 -0500 Subject: [PATCH] NativeQuery support - support for `#addScalar(Class,AttributeConverter)` - support for `#addScalar(Class,Class)` - fixed problem with mapping of converted enums --- design/6.0-changes.adoc | 20 ++ .../internal/InferredBasicValueResolver.java | 7 +- .../internal/NamedConverterResolution.java | 43 +++- .../org/hibernate/mapping/BasicValue.java | 12 +- .../org/hibernate/mapping/SimpleValue.java | 46 ++-- .../metamodel/model/convert/Converters.java | 46 ++++ .../internal/JpaAttributeConverterImpl.java | 12 +- .../internal/NamedEnumValueConverter.java | 2 +- .../convert/spi/JpaAttributeConverter.java | 4 +- .../java/org/hibernate/query/NativeQuery.java | 50 ++-- .../org/hibernate/query/results/Builders.java | 57 +++-- .../query/results/ConvertedResultBuilder.java | 200 ++++++++++++++++ .../query/results/ScalarResultBuilder.java | 90 +------- .../results/StandardScalarResultBuilder.java | 109 +++++++++ .../query/sql/internal/NativeQueryImpl.java | 31 ++- .../query/sql/spi/NativeQueryImplementor.java | 7 + .../type/AbstractStandardBasicType.java | 12 +- .../java/org/hibernate/type/EnumType.java | 63 +++-- .../AttributeConverterTypeAdapter.java | 63 +++-- .../converter/ConvertedValueExtractor.java | 65 ++++++ .../java/CharacterTypeDescriptor.java | 6 + .../java/EnumJavaTypeDescriptor.java | 6 + .../spi/JavaTypeDescriptorBasicAdaptor.java | 5 + .../descriptor/sql/CharTypeDescriptor.java | 8 + .../sql/SqlTypeDescriptorIndicators.java | 9 + .../usertype/DynamicParameterizedType.java | 1 + .../test/metamodel/mapping/SmokeTests.java | 69 +++++- .../query/sql/NativeQueryScalarTests.java | 218 ++++++++++++++---- .../orm/domain/gambit/EntityOfBasics.java | 24 +- 29 files changed, 1039 insertions(+), 246 deletions(-) create mode 100644 design/6.0-changes.adoc create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/Converters.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/results/ConvertedResultBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/results/StandardScalarResultBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/ConvertedValueExtractor.java diff --git a/design/6.0-changes.adoc b/design/6.0-changes.adoc new file mode 100644 index 0000000000..022b83c881 --- /dev/null +++ b/design/6.0-changes.adoc @@ -0,0 +1,20 @@ += Changes + +Changes in 6.0 worth documenting in various places + + +== New features + +Document in release-notes: + +* `@SqlTypeCode` +* `@SqlType` +* `@JavaType` +* `NativeQuery#addScalar(Class)` +* `NativeQuery#addScalar(Class,AttributeConverter)` +* `NativeQuery#addScalar(Class,Class)` + + +== Changes + +Document in migration-guide. \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java index 4d8554771e..a1f833b45e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java @@ -173,7 +173,7 @@ public class InferredBasicValueResolver { switch ( enumStyle ) { case STRING: { - final JavaTypeDescriptor relationalJtd; + final JavaTypeDescriptor relationalJtd; if ( explicitJavaType != null ) { if ( ! String.class.isAssignableFrom( explicitJavaType.getJavaType() ) ) { throw new MappingException( @@ -182,11 +182,12 @@ public class InferredBasicValueResolver { " should handle `java.lang.String` as its relational type descriptor" ); } - //noinspection unchecked relationalJtd = explicitJavaType; } else { - relationalJtd = typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class ); + final boolean useCharacter = stdIndicators.getColumnLength() == 1; + final Class relationalJavaType = useCharacter ? Character.class : String.class; + relationalJtd = typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( relationalJavaType ); } final SqlTypeDescriptor std = explicitSqlType != null ? explicitSqlType : relationalJtd.getJdbcRecommendedSqlType( stdIndicators ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java index d9d96d4c3e..db2dc2c1e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java @@ -19,6 +19,8 @@ import org.hibernate.mapping.BasicValue; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -93,6 +95,7 @@ public class NamedConverterResolution implements BasicValue.Resolution { final JavaTypeDescriptor explicitJtd = explicitJtdAccess != null ? explicitJtdAccess.apply( typeConfiguration ) : null; + final JavaTypeDescriptor domainJtd = explicitJtd != null ? explicitJtd : converter.getDomainJavaDescriptor(); @@ -100,7 +103,9 @@ public class NamedConverterResolution implements BasicValue.Resolution { final SqlTypeDescriptor explicitStd = explicitStdAccess != null ? explicitStdAccess.apply( typeConfiguration ) : null; + final JavaTypeDescriptor relationalJtd = converter.getRelationalJavaDescriptor(); + final SqlTypeDescriptor relationalStd = explicitStd != null ? explicitStd : relationalJtd.getJdbcRecommendedSqlType( sqlTypeIndicators ); @@ -139,13 +144,46 @@ public class NamedConverterResolution implements BasicValue.Resolution { SqlTypeDescriptor relationalStd, JpaAttributeConverter valueConverter, MutabilityPlan mutabilityPlan) { + assert domainJtd != null; this.domainJtd = domainJtd; + + assert relationalJtd != null; this.relationalJtd = relationalJtd; + + assert relationalStd != null; this.relationalStd = relationalStd; + + assert valueConverter != null; this.valueConverter = valueConverter; + + assert mutabilityPlan != null; this.mutabilityPlan = mutabilityPlan; - this.jdbcMapping = new StandardBasicTypeImpl( relationalJtd, relationalStd ).getJdbcMapping(); + this.jdbcMapping = new JdbcMapping() { + private final ValueExtractor extractor = relationalStd.getExtractor( relationalJtd ); + private final ValueBinder binder = relationalStd.getBinder( relationalJtd ); + + @Override + public JavaTypeDescriptor getJavaTypeDescriptor() { + return relationalJtd; + } + + @Override + public SqlTypeDescriptor getSqlTypeDescriptor() { + return relationalStd; + } + + @Override + public ValueExtractor getJdbcValueExtractor() { + return extractor; + } + + @Override + public ValueBinder getJdbcValueBinder() { + return binder; + } + }; +// this.jdbcMapping = new StandardBasicTypeImpl( relationalJtd, relationalStd ); this.legacyResolvedType = new AttributeConverterTypeAdapter( ConverterDescriptor.TYPE_NAME_PREFIX + valueConverter.getConverterJavaTypeDescriptor().getJavaType().getName(), @@ -156,8 +194,7 @@ public class NamedConverterResolution implements BasicValue.Resolution { ), valueConverter, relationalStd, - domainJtd.getJavaType(), - relationalJtd.getJavaType(), + relationalJtd, domainJtd ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index d24514f934..2ff51b284e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -167,6 +167,17 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato return column; } + @Override + public long getColumnLength() { + if ( column != null && column instanceof Column ) { + final Long length = ( (Column) column ).getLength(); + return length == null ? NO_COLUMN_LENGTH : length; + } + else { + return NO_COLUMN_LENGTH; + } + } + @Override public void addColumn(Column incomingColumn) { super.addColumn( incomingColumn ); @@ -330,7 +341,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato ); } - JavaTypeDescriptor jtd = null; // determine JTD if we can diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 42701aa213..529fd17161 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -576,7 +576,7 @@ public abstract class SimpleValue implements KeyValue { } ); - final BasicJavaDescriptor entityAttributeJavaTypeDescriptor = (BasicJavaDescriptor) jpaAttributeConverter.getDomainJavaTypeDescriptor(); + final BasicJavaDescriptor domainJtd = (BasicJavaDescriptor) jpaAttributeConverter.getDomainJavaTypeDescriptor(); // build the SqlTypeDescriptor adapter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -595,7 +595,7 @@ public abstract class SimpleValue implements KeyValue { jdbcTypeCode = LobTypeMappings.getLobCodeTypeMapping( jdbcTypeCode ); } else { - if ( Serializable.class.isAssignableFrom( entityAttributeJavaTypeDescriptor.getJavaType() ) ) { + if ( Serializable.class.isAssignableFrom( domainJtd.getJavaType() ) ) { jdbcTypeCode = Types.BLOB; } else { @@ -642,19 +642,18 @@ public abstract class SimpleValue implements KeyValue { jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType().getSimpleName(), jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType().getSimpleName() ); - return new AttributeConverterTypeAdapter( + return new AttributeConverterTypeAdapter<>( name, description, jpaAttributeConverter, sqlTypeDescriptorAdapter, - jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType(), - jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType(), - entityAttributeJavaTypeDescriptor + jpaAttributeConverter.getRelationalJavaTypeDescriptor(), + jpaAttributeConverter.getDomainJavaTypeDescriptor() ); } public boolean isTypeSpecified() { - return typeName!=null; + return typeName != null; } public void setTypeParameters(Properties parameterMap) { @@ -752,11 +751,15 @@ public abstract class SimpleValue implements KeyValue { private void createParameterImpl() { try { - String[] columnsNames = new String[columns.size()]; + final String[] columnNames = new String[ columns.size() ]; + final Long[] columnLengths = new Long[ columns.size() ]; + for ( int i = 0; i < columns.size(); i++ ) { - Selectable column = columns.get(i); - if (column instanceof Column){ - columnsNames[i] = ((Column) column).getName(); + final Selectable selectable = columns.get(i); + if ( selectable instanceof Column ) { + final Column column = (Column) selectable; + columnNames[i] = column.getName(); + columnLengths[i] = column.getLength(); } } @@ -781,7 +784,8 @@ public abstract class SimpleValue implements KeyValue { table.getSchema(), table.getName(), Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_PRIMARY_KEY ) ), - columnsNames + columnNames, + columnLengths ) ); } @@ -799,9 +803,17 @@ public abstract class SimpleValue implements KeyValue { private final String table; private final boolean primaryKey; private final String[] columns; + private final Long[] columnLengths; - private ParameterTypeImpl(Class returnedClass, Annotation[] annotationsMethod, String catalog, String schema, - String table, boolean primaryKey, String[] columns) { + private ParameterTypeImpl( + Class returnedClass, + Annotation[] annotationsMethod, + String catalog, + String schema, + String table, + boolean primaryKey, + String[] columns, + Long[] columnLengths) { this.returnedClass = returnedClass; this.annotationsMethod = annotationsMethod; this.catalog = catalog; @@ -809,6 +821,7 @@ public abstract class SimpleValue implements KeyValue { this.table = table; this.primaryKey = primaryKey; this.columns = columns; + this.columnLengths = columnLengths; } @Override @@ -845,5 +858,10 @@ public abstract class SimpleValue implements KeyValue { public String[] getColumns() { return columns; } + + @Override + public Long[] getColumnLengths() { + return columnLengths; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/Converters.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/Converters.java new file mode 100644 index 0000000000..8478ae790c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/Converters.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.metamodel.model.convert; + +import javax.persistence.AttributeConverter; + +import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Factory for converter instances + * + * @author Steve Ebersole + */ +public class Converters { + public static BasicValueConverter jpaAttributeConverter( + JavaTypeDescriptor relationalJtd, + JavaTypeDescriptor domainJtd, + Class> converterClass, + SessionFactory factory) { + final SessionFactoryImplementor sfi = (SessionFactoryImplementor) factory; + + final ManagedBeanRegistry beanRegistry = sfi.getServiceRegistry().getService( ManagedBeanRegistry.class ); + final ManagedBean> converterBean = beanRegistry.getBean( converterClass ); + + final TypeConfiguration typeConfiguration = sfi.getTypeConfiguration(); + final JavaTypeDescriptorRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); + final JavaTypeDescriptor> converterJtd = jtdRegistry.getDescriptor( converterClass ); + + return new JpaAttributeConverterImpl<>( converterBean, converterJtd, domainJtd, relationalJtd ); + } + + private Converters() { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java index facebeaceb..55d0e649e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java @@ -18,14 +18,14 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * @author Steve Ebersole */ public class JpaAttributeConverterImpl implements JpaAttributeConverter { - private final ManagedBean> attributeConverterBean; - private final JavaTypeDescriptor> converterJavaTypeDescriptor; + private final ManagedBean> attributeConverterBean; + private final JavaTypeDescriptor> converterJavaTypeDescriptor; private final JavaTypeDescriptor domainJavaTypeDescriptor; private final JavaTypeDescriptor relationalJavaTypeDescriptor; public JpaAttributeConverterImpl( - ManagedBean> attributeConverterBean, - JavaTypeDescriptor> converterJavaTypeDescriptor, + ManagedBean> attributeConverterBean, + JavaTypeDescriptor> converterJavaTypeDescriptor, JavaTypeDescriptor domainJavaTypeDescriptor, JavaTypeDescriptor relationalJavaTypeDescriptor) { this.attributeConverterBean = attributeConverterBean; @@ -35,7 +35,7 @@ public class JpaAttributeConverterImpl implements JpaAttributeConverter> getConverterBean() { + public ManagedBean> getConverterBean() { return attributeConverterBean; } @@ -50,7 +50,7 @@ public class JpaAttributeConverterImpl implements JpaAttributeConverter> getConverterJavaTypeDescriptor() { + public JavaTypeDescriptor> getConverterJavaTypeDescriptor() { return converterJavaTypeDescriptor; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java index 74941c2195..85c31075b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java @@ -70,7 +70,7 @@ public class NamedEnumValueConverter> implements EnumValueConv @Override public int getJdbcTypeCode() { - return Types.VARCHAR; + return sqlTypeDescriptor.getJdbcTypeCode(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java index a5f9710f64..d63c5e7ca8 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/JpaAttributeConverter.java @@ -17,9 +17,9 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * @author Steve Ebersole */ public interface JpaAttributeConverter extends BasicValueConverter { - JavaTypeDescriptor> getConverterJavaTypeDescriptor(); + JavaTypeDescriptor> getConverterJavaTypeDescriptor(); - ManagedBean> getConverterBean(); + ManagedBean> getConverterBean(); JavaTypeDescriptor getDomainJavaTypeDescriptor(); JavaTypeDescriptor getRelationalJavaTypeDescriptor(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java index 7a04a5bfb7..8fd59ce24e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java @@ -14,6 +14,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; +import javax.persistence.AttributeConverter; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Parameter; @@ -76,20 +77,6 @@ public interface NativeQuery extends Query, SynchronizeableQuery { */ NativeQuery addScalar(String columnAlias); - /** - * Declare a scalar query result. - *

- * Functions like {@code } in {@code hbm.xml} or - * {@link javax.persistence.ColumnResult} in annotations - * - * @param columnAlias The column alias in the result-set to be processed - * as a scalar result - * @param type The Hibernate type as which to treat the value. - * - * @return {@code this}, for method chaining - */ -// NativeQuery addScalar(String columnAlias, Type type); - /** * Declare a scalar query result. *

@@ -105,12 +92,45 @@ public interface NativeQuery extends Query, SynchronizeableQuery { NativeQuery addScalar(String columnAlias, BasicDomainType type); /** - * Declare a scalar query result with an explicit return type + * Declare a scalar query result using the specified result type. + * + * Hibernate will implicitly determine an appropriate conversion, if + * it can. Otherwise an exception will be thrown * * @return {@code this}, for method chaining + * + * @since 6.0 */ NativeQuery addScalar(String columnAlias, Class javaType); + /** + * Declare a scalar query result with an explicit conversion + * + * @param relationalJavaType The Java type expected by the converter as its + * "relational" type. + * @param converter The conversion to apply. Consumes the JDBC value based + * on `relationalJavaType`. + * + * @return {@code this}, for method chaining + * + * @since 6.0 + */ + NativeQuery addScalar(String columnAlias, Class relationalJavaType, AttributeConverter converter); + + /** + * Declare a scalar query result with an explicit conversion + * + * @param relationalJavaType The Java type expected by the converter as its + * "relational" type. + * @param converter The conversion to apply. Consumes the JDBC value based + * on `relationalJavaType`. + * + * @return {@code this}, for method chaining + * + * @since 6.0 + */ + NativeQuery addScalar(String columnAlias, Class relationalJavaType, Class> converter); + /** * Add a new root return mapping, returning a {@link RootReturn} to allow * further definition. diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java index e376bc130a..13d3681640 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java @@ -6,55 +6,62 @@ */ package org.hibernate.query.results; -import java.util.function.Consumer; +import javax.persistence.AttributeConverter; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.BasicJavaDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Steve Ebersole */ public class Builders { public static ScalarResultBuilder scalar(String columnAlias) { - return new ScalarResultBuilder( columnAlias ); + return new StandardScalarResultBuilder( columnAlias ); } public static ScalarResultBuilder scalar( String columnAlias, BasicType type) { - return new ScalarResultBuilder( columnAlias, type ); + return new StandardScalarResultBuilder( columnAlias, type ); } public static ScalarResultBuilder scalar( String columnAlias, Class javaType, SessionFactoryImplementor factory) { - return new ScalarResultBuilder( - columnAlias, - factory.getTypeConfiguration().standardBasicTypeForJavaType( javaType ) - ); + final JavaTypeDescriptor javaTypeDescriptor = factory.getTypeConfiguration() + .getJavaTypeDescriptorRegistry() + .getDescriptor( javaType ); + + return new StandardScalarResultBuilder( columnAlias, javaTypeDescriptor ); } - public static DomainResult implicitScalarDomainResult( - int colIndex, - String columnName, - JdbcValuesMetadata jdbcResultsMetadata, - Consumer sqlSelectionConsumer, + public static ResultBuilder scalar( + String columnAlias, + Class relationalJavaType, + AttributeConverter converter, SessionFactoryImplementor sessionFactory) { - final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); - final SqlTypeDescriptor sqlTypeDescriptor = jdbcResultsMetadata.resolveSqlTypeDescriptor( colIndex ); - final BasicJavaDescriptor javaTypeDescriptor = sqlTypeDescriptor.getJdbcRecommendedJavaTypeMapping( typeConfiguration ); - final BasicType jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( javaTypeDescriptor, sqlTypeDescriptor ); - sqlSelectionConsumer.accept( new SqlSelectionImpl( colIndex, jdbcMapping ) ); - return new BasicResult<>( colIndex, columnName, javaTypeDescriptor ); + return ConvertedResultBuilder.from( columnAlias, relationalJavaType, converter, sessionFactory ); + } + + public static ResultBuilder scalar( + String columnAlias, + Class relationalJavaType, + Class> converterJavaType, + SessionFactoryImplementor sessionFactory) { + return ConvertedResultBuilder.from( columnAlias, relationalJavaType, converterJavaType, sessionFactory ); + } + + public static ScalarResultBuilder scalar(int position) { + // will be needed for interpreting legacy HBM mappings + throw new NotYetImplementedFor6Exception(); + } + + public static ScalarResultBuilder scalar(int position, BasicType type) { + // will be needed for interpreting legacy HBM mappings + throw new NotYetImplementedFor6Exception(); } public static EntityResultBuilder entity(String tableAlias, String entityName) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ConvertedResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ConvertedResultBuilder.java new file mode 100644 index 0000000000..d4d983a354 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ConvertedResultBuilder.java @@ -0,0 +1,200 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.results; + +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import javax.persistence.AttributeConverter; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.converter.ConvertedValueExtractor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * A ResultBuilder for explicitly converted scalar results + * + * @author Steve Ebersole + */ +public class ConvertedResultBuilder implements ScalarResultBuilder { + + public static ResultBuilder from( + String columnAlias, + Class relationalJavaType, + AttributeConverter converter, + SessionFactoryImplementor sessionFactory) { + final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + final JavaTypeDescriptorRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); + final JavaTypeDescriptor relationJtd = jtdRegistry.getDescriptor( relationalJavaType ); + + return new ConvertedResultBuilder( columnAlias, relationJtd, converter ); + } + + public static ResultBuilder from( + String columnAlias, + Class relationalJavaType, + Class> converterJavaType, + SessionFactoryImplementor sessionFactory) { + final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + final JavaTypeDescriptorRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); + final JavaTypeDescriptor relationJtd = jtdRegistry.getDescriptor( relationalJavaType ); + + final ManagedBeanRegistry beans = sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class ); + final ManagedBean> bean = beans.getBean( converterJavaType ); + final AttributeConverter converter = bean.getBeanInstance(); + + return new ConvertedResultBuilder( columnAlias, relationJtd, converter ); + } + + private final String columnAlias; + private final JavaTypeDescriptor relationJtd; + private final AttributeConverter converter; + + public ConvertedResultBuilder( + String columnAlias, + JavaTypeDescriptor relationJtd, + AttributeConverter converter) { + assert columnAlias != null; + this.columnAlias = columnAlias; + + assert relationJtd != null; + this.relationJtd = relationJtd; + + assert converter != null; + this.converter = converter; + + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public DomainResult buildReturn( + JdbcValuesMetadata jdbcResultsMetadata, + BiFunction legacyFetchResolver, + Consumer sqlSelectionConsumer, + SessionFactoryImplementor sessionFactory) { + final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( columnAlias ); + final int valuesArrayPosition = jdbcPosition - 1; + + final SqlTypeDescriptor std = jdbcResultsMetadata.resolveSqlTypeDescriptor( jdbcPosition ); + + final ConverterMapping converterMapping = new ConverterMapping( + relationJtd, + std, + new ConvertedValueExtractor( std.getExtractor( relationJtd ), converter ) + ); + + final SqlSelectionImpl sqlSelection = new SqlSelectionImpl( valuesArrayPosition, converterMapping ); + sqlSelectionConsumer.accept( sqlSelection ); + + return new BasicResult( valuesArrayPosition, columnAlias, converterMapping.getJavaTypeDescriptor() ); + } + + + private static class ConverterMapping implements MappingModelExpressable, JdbcMapping { + private final JavaTypeDescriptor jtd; + private final SqlTypeDescriptor std; + + private final ValueExtractor extractor; + + public ConverterMapping( + JavaTypeDescriptor jtd, + SqlTypeDescriptor std, + ValueExtractor extractor) { + this.jtd = jtd; + this.std = std; + this.extractor = extractor; + } + + @Override + public int getJdbcTypeCount(TypeConfiguration typeConfiguration) { + return 1; + } + + @Override + public JavaTypeDescriptor getJavaTypeDescriptor() { + return jtd; + } + + @Override + public SqlTypeDescriptor getSqlTypeDescriptor() { + return std; + } + + @Override + public ValueExtractor getJdbcValueExtractor() { + return extractor; + } + + @Override + public ValueBinder getJdbcValueBinder() { + // this will never get used for binding values + throw new UnsupportedOperationException(); + } + + @Override + public List getJdbcMappings(TypeConfiguration typeConfiguration) { + return Collections.singletonList( this ); + } + + @Override + public void visitJdbcTypes( + Consumer action, + Clause clause, + TypeConfiguration typeConfiguration) { + action.accept( this ); + } + } + + @SuppressWarnings("rawtypes") + private static class SqlSelectionImpl implements SqlSelection { + private final int valuesArrayPosition; + private final ConverterMapping converterMapping; + + public SqlSelectionImpl( + int valuesArrayPosition, + ConverterMapping converterMapping) { + this.valuesArrayPosition = valuesArrayPosition; + this.converterMapping = converterMapping; + } + + @Override + public int getValuesArrayPosition() { + return valuesArrayPosition; + } + + @Override + public ValueExtractor getJdbcValueExtractor() { + return converterMapping.getJdbcValueExtractor(); + } + + @Override + public MappingModelExpressable getExpressionType() { + return converterMapping; + } + + @Override + public void accept(SqlAstWalker sqlAstWalker) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ScalarResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ScalarResultBuilder.java index e8e147a8fc..8a85d385e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ScalarResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ScalarResultBuilder.java @@ -6,96 +6,10 @@ */ package org.hibernate.query.results; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; -import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.spi.TypeConfiguration; - /** - * @see javax.persistence.ColumnResult + * Nominal extension to ResultBuilder for cases involving scalar results * * @author Steve Ebersole */ -public class ScalarResultBuilder implements ResultBuilder { - private final String explicitName; - - private final BasicType explicitType; - private final JavaTypeDescriptor explicitJavaTypeDescriptor; - - ScalarResultBuilder(String explicitName, BasicType explicitType) { - assert explicitName != null; - this.explicitName = explicitName; - - assert explicitType != null; - this.explicitType = explicitType; - - this.explicitJavaTypeDescriptor = null; - } - - ScalarResultBuilder(String explicitName, JavaTypeDescriptor explicitJavaTypeDescriptor) { - assert explicitName != null; - this.explicitName = explicitName; - - assert explicitJavaTypeDescriptor != null; - this.explicitJavaTypeDescriptor = explicitJavaTypeDescriptor; - - this.explicitType = null; - } - - public ScalarResultBuilder(String explicitName) { - assert explicitName != null; - this.explicitName = explicitName; - - this.explicitType = null; - this.explicitJavaTypeDescriptor = null; - } - - public String getExplicitName() { - return explicitName; - } - - @Override - public DomainResult buildReturn( - JdbcValuesMetadata jdbcResultsMetadata, - BiFunction legacyFetchResolver, - Consumer sqlSelectionConsumer, - SessionFactoryImplementor sessionFactory) { - final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( explicitName ); - final int valuesArrayPosition = jdbcPosition - 1; - - final BasicType jdbcMapping; - - if ( explicitType != null ) { - jdbcMapping = explicitType; - } - else if ( explicitJavaTypeDescriptor != null ) { - final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); - - final SqlTypeDescriptor sqlTypeDescriptor = jdbcResultsMetadata.resolveSqlTypeDescriptor( jdbcPosition ); - - jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( explicitJavaTypeDescriptor, sqlTypeDescriptor ); - } - else { - final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); - - final SqlTypeDescriptor sqlTypeDescriptor = jdbcResultsMetadata.resolveSqlTypeDescriptor( jdbcPosition ); - final JavaTypeDescriptor javaTypeDescriptor = sqlTypeDescriptor.getJdbcRecommendedJavaTypeMapping( typeConfiguration ); - - jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( javaTypeDescriptor, sqlTypeDescriptor ); - } - - final SqlSelectionImpl sqlSelection = new SqlSelectionImpl( valuesArrayPosition, jdbcMapping ); - sqlSelectionConsumer.accept( sqlSelection ); - - return new BasicResult( valuesArrayPosition, explicitName, jdbcMapping.getJavaTypeDescriptor() ); - } - +public interface ScalarResultBuilder extends ResultBuilder { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/StandardScalarResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/StandardScalarResultBuilder.java new file mode 100644 index 0000000000..40e6c36c54 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/StandardScalarResultBuilder.java @@ -0,0 +1,109 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.results; + +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * @see javax.persistence.ColumnResult + * + * @author Steve Ebersole + */ +public class StandardScalarResultBuilder implements ScalarResultBuilder { + private final String explicitName; + + private final BasicType explicitType; + private final JavaTypeDescriptor explicitJavaTypeDescriptor; + + public StandardScalarResultBuilder(String explicitName) { + assert explicitName != null; + this.explicitName = explicitName; + + this.explicitType = null; + this.explicitJavaTypeDescriptor = null; + } + + StandardScalarResultBuilder(String explicitName, BasicType explicitType) { + assert explicitName != null; + this.explicitName = explicitName; + + assert explicitType != null; + this.explicitType = explicitType; + + this.explicitJavaTypeDescriptor = null; + } + + StandardScalarResultBuilder(String explicitName, JavaTypeDescriptor explicitJavaTypeDescriptor) { + assert explicitName != null; + this.explicitName = explicitName; + + assert explicitJavaTypeDescriptor != null; + this.explicitJavaTypeDescriptor = explicitJavaTypeDescriptor; + + this.explicitType = null; + } + + StandardScalarResultBuilder(JavaTypeDescriptor explicitJavaTypeDescriptor) { + assert explicitJavaTypeDescriptor != null; + this.explicitJavaTypeDescriptor = explicitJavaTypeDescriptor; + + this.explicitName = null; + this.explicitType = null; + } + + public String getExplicitName() { + return explicitName; + } + + @Override + public DomainResult buildReturn( + JdbcValuesMetadata jdbcResultsMetadata, + BiFunction legacyFetchResolver, + Consumer sqlSelectionConsumer, + SessionFactoryImplementor sessionFactory) { + final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( explicitName ); + final int valuesArrayPosition = jdbcPosition - 1; + + final BasicType jdbcMapping; + + if ( explicitType != null ) { + jdbcMapping = explicitType; + } + else if ( explicitJavaTypeDescriptor != null ) { + final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + + final SqlTypeDescriptor sqlTypeDescriptor = jdbcResultsMetadata.resolveSqlTypeDescriptor( jdbcPosition ); + + jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( explicitJavaTypeDescriptor, sqlTypeDescriptor ); + } + else { + final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + + final SqlTypeDescriptor sqlTypeDescriptor = jdbcResultsMetadata.resolveSqlTypeDescriptor( jdbcPosition ); + final JavaTypeDescriptor javaTypeDescriptor = sqlTypeDescriptor.getJdbcRecommendedJavaTypeMapping( typeConfiguration ); + + jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( javaTypeDescriptor, sqlTypeDescriptor ); + } + + final SqlSelectionImpl sqlSelection = new SqlSelectionImpl( valuesArrayPosition, jdbcMapping ); + sqlSelectionConsumer.accept( sqlSelection ); + + return new BasicResult( valuesArrayPosition, explicitName, jdbcMapping.getJavaTypeDescriptor() ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 0877452b13..46f8f168c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.persistence.AttributeConverter; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.FlushModeType; @@ -58,6 +59,7 @@ import org.hibernate.query.internal.QueryParameterBindingsImpl; import org.hibernate.query.results.Builders; import org.hibernate.query.results.EntityResultBuilder; import org.hibernate.query.results.LegacyFetchBuilder; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultSetMappingImpl; import org.hibernate.query.spi.AbstractQuery; import org.hibernate.query.spi.MutableQueryOptions; @@ -475,22 +477,41 @@ public class NativeQueryImpl @Override public NativeQueryImplementor addScalar(String columnAlias) { - resultSetMapping.addResultBuilder( Builders.scalar( columnAlias ) ); + return registerBuilder( Builders.scalar( columnAlias ) ); + } + + protected NativeQueryImplementor registerBuilder(ResultBuilder builder) { + resultSetMapping.addResultBuilder( builder ); return this; } @Override public NativeQueryImplementor addScalar(String columnAlias, BasicDomainType type) { - resultSetMapping.addResultBuilder( Builders.scalar( columnAlias, (BasicType) type ) ); - return this; + return registerBuilder( Builders.scalar( columnAlias, (BasicType) type ) ); } @Override public NativeQueryImplementor addScalar(String columnAlias, Class javaType) { - resultSetMapping.addResultBuilder( Builders.scalar( columnAlias, javaType, getSessionFactory() ) ); - return this; + return registerBuilder( Builders.scalar( columnAlias, javaType, getSessionFactory() ) ); } + @Override + public NativeQueryImplementor addScalar( + String columnAlias, + Class relationalJavaType, + AttributeConverter converter) { + return registerBuilder( Builders.scalar( columnAlias, relationalJavaType, converter, getSessionFactory() ) ); + } + + @Override + public NativeQueryImplementor addScalar( + String columnAlias, + Class relationalJavaType, + Class> converter) { + return registerBuilder( Builders.scalar( columnAlias, relationalJavaType, converter, getSessionFactory() ) ); + } + + @Override public EntityResultBuilder addRoot(String tableAlias, String entityName) { final EntityResultBuilder resultBuilder = Builders.entity( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java index c244abbc63..9ae531be57 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java @@ -14,6 +14,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; +import javax.persistence.AttributeConverter; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Parameter; @@ -58,6 +59,12 @@ public interface NativeQueryImplementor extends QueryImplementor, NativeQu @Override NativeQueryImplementor addScalar(String columnAlias, Class javaType); + @Override + NativeQueryImplementor addScalar(String columnAlias, Class relationalJavaType, AttributeConverter converter); + + @Override + NativeQueryImplementor addScalar(String columnAlias, Class relationalJavaType, Class> converter); + @Override EntityResultBuilder addRoot(String tableAlias, String entityName); 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 a858d238ff..34f0ee2914 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -306,7 +306,7 @@ public abstract class AbstractStandardBasicType } @SuppressWarnings({ "unchecked" }) - protected final void nullSafeSet(PreparedStatement st, Object value, int index, WrapperOptions options) throws SQLException { + protected void nullSafeSet(PreparedStatement st, Object value, int index, WrapperOptions options) throws SQLException { remapSqlTypeDescriptor( options ).getBinder( javaTypeDescriptor ).bind( st, ( T ) value, index, options ); } @@ -430,6 +430,16 @@ public abstract class AbstractStandardBasicType ); } + @Override + public void nullSafeSet( + PreparedStatement st, + Object value, + int index, + boolean[] settable, + SharedSessionContractImplementor session) throws SQLException { + + } + @Override public void nullSafeSet(CallableStatement st, Object value, String name, SharedSessionContractImplementor session) throws SQLException { nullSafeSet( st, value, name, (WrapperOptions) session ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index c689233dfc..6a99bc5305 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -64,7 +64,7 @@ import org.jboss.logging.Logger; * @author Steve Ebersole */ @SuppressWarnings("unchecked") -public class EnumType +public class EnumType> implements EnhancedUserType, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable { private static final Logger LOG = CoreLogging.logger( EnumType.class ); @@ -74,7 +74,7 @@ public class EnumType private Class enumClass; - private EnumValueConverter enumValueConverter; + private EnumValueConverter enumValueConverter; private TypeConfiguration typeConfiguration; @@ -106,6 +106,8 @@ public class EnumType if ( reader != null ) { enumClass = reader.getReturnedClass().asSubclass( Enum.class ); + final Long columnLength = reader.getColumnLengths()[0]; + final boolean isOrdinal; final javax.persistence.EnumType enumType = getEnumType( reader ); if ( enumType == null ) { @@ -125,28 +127,31 @@ public class EnumType .getJavaTypeDescriptorRegistry() .getDescriptor( enumClass ); - final BasicJavaDescriptor relationalJavaDescriptor = resolveRelationalJavaTypeDescriptor( - reader, + final LocalSqlTypeDescriptorIndicators indicators = new LocalSqlTypeDescriptorIndicators( enumType, + columnLength, + reader + ); + + final BasicJavaDescriptor relationalJtd = resolveRelationalJavaTypeDescriptor( + indicators, enumJavaDescriptor ); - final SqlTypeDescriptor sqlTypeDescriptor = relationalJavaDescriptor.getJdbcRecommendedSqlType( - new LocalSqlTypeDescriptorIndicators( enumType, reader ) - ); + final SqlTypeDescriptor sqlTypeDescriptor = relationalJtd.getJdbcRecommendedSqlType( indicators ); if ( isOrdinal ) { this.enumValueConverter = new OrdinalEnumValueConverter( enumJavaDescriptor, sqlTypeDescriptor, - relationalJavaDescriptor + relationalJtd ); } else { this.enumValueConverter = new NamedEnumValueConverter( enumJavaDescriptor, sqlTypeDescriptor, - relationalJavaDescriptor + relationalJtd ); } } @@ -172,10 +177,9 @@ public class EnumType } private BasicJavaDescriptor resolveRelationalJavaTypeDescriptor( - ParameterType reader, - javax.persistence.EnumType enumType, EnumJavaTypeDescriptor enumJavaDescriptor) { - return enumJavaDescriptor.getJdbcRecommendedSqlType( new LocalSqlTypeDescriptorIndicators( enumType, reader ) ) - .getJdbcRecommendedJavaTypeMapping( typeConfiguration ); + LocalSqlTypeDescriptorIndicators indicators, + EnumJavaTypeDescriptor enumJavaDescriptor) { + return enumJavaDescriptor.getJdbcRecommendedSqlType( indicators ).getJdbcRecommendedJavaTypeMapping( typeConfiguration ); } private javax.persistence.EnumType getEnumType(ParameterType reader) { @@ -204,21 +208,26 @@ public class EnumType return null; } - private EnumValueConverter interpretParameters(Properties parameters) { - final EnumJavaTypeDescriptor enumJavaDescriptor = (EnumJavaTypeDescriptor) typeConfiguration + private EnumValueConverter interpretParameters(Properties parameters) { + final EnumJavaTypeDescriptor enumJavaDescriptor = (EnumJavaTypeDescriptor) typeConfiguration .getJavaTypeDescriptorRegistry() .getDescriptor( enumClass ); final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE ); final javax.persistence.EnumType enumType = getEnumType( reader ); - final LocalSqlTypeDescriptorIndicators localIndicators = new LocalSqlTypeDescriptorIndicators( enumType, reader ); - final BasicJavaDescriptor stringJavaDescriptor = (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class ); - final BasicJavaDescriptor integerJavaDescriptor = (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class ); + final LocalSqlTypeDescriptorIndicators localIndicators = new LocalSqlTypeDescriptorIndicators( + enumType, + reader.getColumnLengths()[0], + reader + ); + final BasicJavaDescriptor stringJavaDescriptor = (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class ); + final BasicJavaDescriptor integerJavaDescriptor = (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class ); if ( parameters.containsKey( NAMED ) ) { final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters ); if ( useNamed ) { + //noinspection rawtypes return new NamedEnumValueConverter( enumJavaDescriptor, stringJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), @@ -226,6 +235,7 @@ public class EnumType ); } else { + //noinspection rawtypes return new OrdinalEnumValueConverter( enumJavaDescriptor, integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), @@ -237,6 +247,7 @@ public class EnumType if ( parameters.containsKey( TYPE ) ) { final int type = Integer.decode( (String) parameters.get( TYPE ) ); if ( isNumericType( type ) ) { + //noinspection rawtypes return new OrdinalEnumValueConverter( enumJavaDescriptor, integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), @@ -244,6 +255,7 @@ public class EnumType ); } else if ( isCharacterType( type ) ) { + //noinspection rawtypes return new NamedEnumValueConverter( enumJavaDescriptor, stringJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), @@ -336,7 +348,7 @@ public class EnumType @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { verifyConfigured(); - enumValueConverter.writeValue( st, (Enum) value, index, session ); + enumValueConverter.writeValue( st, (T) value, index, session ); } @Override @@ -383,7 +395,7 @@ public class EnumType @Override public String toXMLString(Object value) { verifyConfigured(); - return (String) enumValueConverter.getDomainJavaDescriptor().unwrap( (Enum) value, String.class, null ); + return enumValueConverter.getDomainJavaDescriptor().unwrap( (T) value, String.class, null ); } @Override @@ -396,7 +408,7 @@ public class EnumType @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) { verifyConfigured(); - return enumValueConverter.getDomainJavaDescriptor().toString( (Enum) value ); + return enumValueConverter.getDomainJavaDescriptor().toString( (T) value ); } public boolean isOrdinal() { @@ -406,10 +418,12 @@ public class EnumType private class LocalSqlTypeDescriptorIndicators implements SqlTypeDescriptorIndicators { private final javax.persistence.EnumType enumType; + private final Long columnLength; private final ParameterType reader; - public LocalSqlTypeDescriptorIndicators(javax.persistence.EnumType enumType, ParameterType reader) { + public LocalSqlTypeDescriptorIndicators(javax.persistence.EnumType enumType, Long columnLength, ParameterType reader) { this.enumType = enumType; + this.columnLength = columnLength; this.reader = reader; } @@ -444,5 +458,10 @@ public class EnumType return false; } + + @Override + public long getColumnLength() { + return columnLength == null ? NO_COLUMN_LENGTH : columnLength; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java index a3d77383b4..8bb0714586 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java @@ -6,9 +6,17 @@ */ package org.hibernate.type.descriptor.converter; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.AttributeConverter; + import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.MutabilityPlan; @@ -24,13 +32,14 @@ import org.jboss.logging.Logger; public class AttributeConverterTypeAdapter extends AbstractSingleColumnStandardBasicType { private static final Logger log = Logger.getLogger( AttributeConverterTypeAdapter.class ); + @SuppressWarnings("unused") public static final String NAME_PREFIX = ConverterDescriptor.TYPE_NAME_PREFIX; private final String name; private final String description; - private final Class modelType; - private final Class jdbcType; + private final JavaTypeDescriptor domainJtd; + private final JavaTypeDescriptor relationalJtd; private final JpaAttributeConverter attributeConverter; private final MutabilityPlan mutabilityPlan; @@ -40,24 +49,22 @@ public class AttributeConverterTypeAdapter extends AbstractSingleColumnStanda String name, String description, JpaAttributeConverter attributeConverter, - SqlTypeDescriptor sqlTypeDescriptorAdapter, - Class modelType, - Class jdbcType, - JavaTypeDescriptor entityAttributeJavaTypeDescriptor) { - super( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor ); + SqlTypeDescriptor std, + JavaTypeDescriptor relationalJtd, + JavaTypeDescriptor domainJtd) { + //noinspection rawtypes + super( std, (JavaTypeDescriptor) relationalJtd ); this.name = name; this.description = description; - this.modelType = modelType; - this.jdbcType = jdbcType; + this.domainJtd = domainJtd; + this.relationalJtd = relationalJtd; this.attributeConverter = attributeConverter; - this.mutabilityPlan = entityAttributeJavaTypeDescriptor.getMutabilityPlan().isMutable() + this.mutabilityPlan = domainJtd.getMutabilityPlan().isMutable() ? new AttributeConverterMutabilityPlanImpl<>( attributeConverter ) : ImmutableMutabilityPlan.INSTANCE; log.debugf( "Created AttributeConverterTypeAdapter -> %s", name ); - -// throw new UnsupportedOperationException( ); } @Override @@ -65,18 +72,42 @@ public class AttributeConverterTypeAdapter extends AbstractSingleColumnStanda return name; } - public Class getModelType() { - return modelType; + public JavaTypeDescriptor getDomainJtd() { + return domainJtd; } - public Class getJdbcType() { - return jdbcType; + public JavaTypeDescriptor getRelationalJtd() { + return relationalJtd; } public JpaAttributeConverter getAttributeConverter() { return attributeConverter; } + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void nullSafeSet( + CallableStatement st, + Object value, + String name, + SharedSessionContractImplementor session) throws SQLException { + final AttributeConverter converter = attributeConverter.getConverterBean().getBeanInstance(); + final Object converted = converter.convertToDatabaseColumn( value ); + super.nullSafeSet( st, converted, name, session ); + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + protected void nullSafeSet(PreparedStatement st, Object value, int index, WrapperOptions options) throws SQLException { + final AttributeConverter converter = attributeConverter.getConverterBean().getBeanInstance(); + final Object converted = converter.convertToDatabaseColumn( value ); + super.nullSafeSet( st, converted, index, options ); + } + + + + + @Override protected MutabilityPlan getMutabilityPlan() { return mutabilityPlan; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/ConvertedValueExtractor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/ConvertedValueExtractor.java new file mode 100644 index 0000000000..252b71286c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/ConvertedValueExtractor.java @@ -0,0 +1,65 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.type.descriptor.converter; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.persistence.AttributeConverter; +import javax.persistence.PersistenceException; + +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class ConvertedValueExtractor implements ValueExtractor { + private static final Logger log = Logger.getLogger( ConvertedValueExtractor.class ); + + private final ValueExtractor relationalExtractor; + private final AttributeConverter converter; + + public ConvertedValueExtractor( + ValueExtractor relationalExtractor, + AttributeConverter converter) { + this.relationalExtractor = relationalExtractor; + this.converter = converter; + } + + @Override + public O extract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return doConversion( relationalExtractor.extract( rs, paramIndex, options ) ); + } + + @Override + public O extract(CallableStatement statement, int paramIndex, WrapperOptions options) throws SQLException { + return doConversion( relationalExtractor.extract( statement, paramIndex, options ) ); + } + + @Override + public O extract(CallableStatement statement, String paramName, WrapperOptions options) throws SQLException { + return doConversion( relationalExtractor.extract( statement, paramName, options ) ); + } + + private O doConversion(R extractedValue) { + try { + O convertedValue = converter.convertToEntityAttribute( extractedValue ); + log.debugf( "Converted value on extraction: %s -> %s", extractedValue, convertedValue ); + return convertedValue; + } + catch (PersistenceException pe) { + throw pe; + } + catch (RuntimeException re) { + throw new PersistenceException( "Error attempting to apply AttributeConverter", re ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterTypeDescriptor.java index 5f88ab652e..4d6f8a4e51 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterTypeDescriptor.java @@ -5,10 +5,15 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.type.descriptor.java; +import java.sql.Types; + import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.spi.Primitive; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Character} handling. @@ -25,6 +30,7 @@ public class CharacterTypeDescriptor extends AbstractTypeDescriptor i public String toString(Character value) { return value.toString(); } + @Override public Character fromString(String string) { if ( string.length() != 1 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java index 5180ab8107..23b05f7cbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaTypeDescriptor.java @@ -31,6 +31,12 @@ public class EnumJavaTypeDescriptor> extends AbstractTypeDescr @Override public SqlTypeDescriptor getJdbcRecommendedSqlType(SqlTypeDescriptorIndicators context) { if ( context.getEnumeratedType() != null && context.getEnumeratedType() == EnumType.STRING ) { + if ( context.getColumnLength() == 1 ) { + return context.isNationalized() + ? context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.NCHAR ) + : context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.CHAR ); + } + return context.isNationalized() ? context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.NVARCHAR ) : context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorBasicAdaptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorBasicAdaptor.java index ff21a1e013..441c7702d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorBasicAdaptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeDescriptorBasicAdaptor.java @@ -54,4 +54,9 @@ public class JavaTypeDescriptorBasicAdaptor extends AbstractTypeDescriptor "Wrap strategy not known for this Java type : " + getJavaType().getName() ); } + + @Override + public String toString() { + return "JavaTypeDescriptorBasicAdaptor(" + getJavaType().getName() + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java index a6d03c2a1c..eaecff650a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java @@ -7,6 +7,9 @@ package org.hibernate.type.descriptor.sql; import java.sql.Types; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + /** * Descriptor for {@link Types#CHAR CHAR} handling. * @@ -27,4 +30,9 @@ public class CharTypeDescriptor extends VarcharTypeDescriptor { public int getSqlType() { return Types.CHAR; } + + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return super.getJdbcRecommendedJavaTypeMapping( typeConfiguration ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorIndicators.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorIndicators.java index f0c3109905..fd55ccd3fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorIndicators.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorIndicators.java @@ -21,6 +21,8 @@ import org.hibernate.type.spi.TypeConfiguration; * @author Steve Ebersole */ public interface SqlTypeDescriptorIndicators { + int NO_COLUMN_LENGTH = -1; + /** * Was nationalized character datatype requested for the given Java type? * @@ -65,6 +67,13 @@ public interface SqlTypeDescriptorIndicators { return Types.BOOLEAN; } + /** + * Useful for resolutions based on column length. E.g. choosing between a VARCHAR (String) and a CHAR(1) (Character/char) + */ + default long getColumnLength() { + return NO_COLUMN_LENGTH; + } + /** * Provides access to the TypeConfiguration for access to various type-system registries. */ diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java index f6539a12f1..2f03b3a170 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/DynamicParameterizedType.java @@ -46,5 +46,6 @@ public interface DynamicParameterizedType extends ParameterizedType { String[] getColumns(); + Long[] getColumnLengths(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/SmokeTests.java index b832275145..bdcef5d3bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/SmokeTests.java @@ -8,6 +8,9 @@ package org.hibernate.orm.test.metamodel.mapping; import java.sql.Statement; import java.sql.Types; +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.Embeddable; import javax.persistence.Embedded; import javax.persistence.Entity; @@ -25,6 +28,7 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter; import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.hamcrest.CollectionMatchers; @@ -40,6 +44,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; /** @@ -60,9 +65,10 @@ public class SmokeTests { .getEntityDescriptor( SimpleEntity.class ); final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - assert Integer.class.equals( identifierMapping.getMappedTypeDescriptor() - .getMappedJavaTypeDescriptor() - .getJavaType() ); + assertThat( + identifierMapping.getMappedTypeDescriptor().getMappedJavaTypeDescriptor().getJavaType(), + sameInstance( Integer.class ) + ); { final ModelPart namePart = entityDescriptor.findSubPart( "name" ); @@ -105,6 +111,23 @@ public class SmokeTests { assertThat( attrMapping.getJdbcMapping().getSqlTypeDescriptor().getJdbcTypeCode(), is( Types.VARCHAR ) ); } + { + final ModelPart part = entityDescriptor.findSubPart( "gender3" ); + assert part instanceof BasicValuedSingularAttributeMapping; + final BasicValuedSingularAttributeMapping attrMapping = (BasicValuedSingularAttributeMapping) part; + assert "mapping_simple_entity".equals( attrMapping.getContainingTableExpression() ); + assert "gender3".equals( attrMapping.getMappedColumnExpression() ); + + assertThat( attrMapping.getJavaTypeDescriptor().getJavaType(), equalTo( Gender.class ) ); + + final BasicValueConverter valueConverter = attrMapping.getValueConverter(); + assertThat( valueConverter, instanceOf( JpaAttributeConverter.class ) ); + assertThat( valueConverter.getDomainJavaDescriptor(), is( attrMapping.getJavaTypeDescriptor() ) ); + assertThat( valueConverter.getRelationalJavaDescriptor().getJavaType(), equalTo( Character.class ) ); + + assertThat( attrMapping.getJdbcMapping().getSqlTypeDescriptor().getJdbcTypeCode(), is( Types.CHAR ) ); + } + { final ModelPart part = entityDescriptor.findSubPart( "component" ); assert part instanceof EmbeddedAttributeMapping; @@ -223,6 +246,7 @@ public class SmokeTests { private String name; private Gender gender; private Gender gender2; + private Gender gender3; private Component component; @Id @@ -260,6 +284,16 @@ public class SmokeTests { this.gender2 = gender2; } + @Convert( converter = GenderConverter.class ) + @Column( length = 1 ) + public Gender getGender3() { + return gender3; + } + + public void setGender3(Gender gender3) { + this.gender3 = gender3; + } + @Embedded public Component getComponent() { return component; @@ -270,6 +304,35 @@ public class SmokeTests { } } + static class GenderConverter implements AttributeConverter { + + @Override + public Character convertToDatabaseColumn(Gender attribute) { + if ( attribute == null ) { + return null; + } + + if ( attribute == Gender.MALE ) { + return 'M'; + } + + return 'F'; + } + + @Override + public Gender convertToEntityAttribute(Character dbData) { + if ( dbData == null ) { + return null; + } + + if ( 'M' == dbData ) { + return Gender.MALE; + } + + return Gender.FEMALE; + } + } + @Embeddable static class SubComponent { private String subAttribute1; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryScalarTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryScalarTests.java index a40607214f..0e969c9fc2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryScalarTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryScalarTests.java @@ -6,51 +6,70 @@ */ package org.hibernate.orm.test.query.sql; -import java.time.LocalDate; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Types; +import java.time.Instant; import java.util.List; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; +import org.hibernate.orm.test.metamodel.mapping.SmokeTests; import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.testing.orm.domain.StandardDomainModel; -import org.hibernate.testing.orm.domain.contacts.Contact; +import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.hamcrest.CustomMatcher; +import org.hamcrest.Matcher; + +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; /** * @author Steve Ebersole */ @DomainModel( - standardModels = StandardDomainModel.CONTACTS + standardModels = StandardDomainModel.GAMBIT ) @SessionFactory public class NativeQueryScalarTests { + public static final String STRING_VALUE = "a string value"; + public static final String URL_STRING = "http://hibernate.org"; + @Test public void fullyImplicitTest(SessionFactoryScope scope) { scope.inTransaction( session -> { - final String sql = "select gender, first, last, id from contacts"; + final String sql = "select theString, theInteger, id from EntityOfBasics"; final NativeQueryImplementor query = session.createNativeQuery( sql ); final List results = query.list(); assertThat( results.size(), is( 1 ) ); + final Object result = results.get( 0 ); assertThat( result, instanceOf( Object[].class ) ); final Object[] values = (Object[]) result; - assertThat( values.length, is(4 ) ); + assertThat( values.length, is(3 ) ); - assertThat( ( (Number) values[0] ).intValue(), is( Contact.Gender.OTHER.ordinal() ) ); - assertThat( values[1], is( "My First" ) ); - assertThat( values[2], is( "Contact" ) ); - assertThat( values[3], is( 1 ) ); + assertThat( values[ 0 ], is( STRING_VALUE ) ); + assertThat( values[ 1 ], is( 2 ) ); + assertThat( values[ 2 ], is( 1 ) ); } ); } @@ -58,71 +77,192 @@ public class NativeQueryScalarTests { public void explicitOrderTest(SessionFactoryScope scope) { scope.inTransaction( session -> { - final String sql = "select gender, first, last, id from contacts"; + final String sql = "select theString, theInteger, id from EntityOfBasics"; final NativeQueryImplementor query = session.createNativeQuery( sql ); // notice the reverse order from the select clause query.addScalar( "id" ); - query.addScalar( "last" ); - query.addScalar( "first" ); - query.addScalar( "gender" ); + query.addScalar( "theInteger" ); + query.addScalar( "theString" ); final List results = query.list(); assertThat( results.size(), is( 1 ) ); + final Object result = results.get( 0 ); assertThat( result, instanceOf( Object[].class ) ); final Object[] values = (Object[]) result; - assertThat( values.length, is(4 ) ); + assertThat( values.length, is(3 ) ); - assertThat( values[0], is( 1 ) ); - assertThat( values[1], is( "Contact" ) ); - assertThat( values[2], is( "My First" ) ); - assertThat( ( (Number) values[3] ).intValue(), is( Contact.Gender.OTHER.ordinal() ) ); + assertThat( values[ 0 ], is( 1 ) ); + assertThat( values[ 1 ], is( 2 ) ); + assertThat( values[ 2 ], is( STRING_VALUE ) ); } ); } @Test -// @FailureExpected( reason = "Explicit type support not working atm" ) public void explicitEnumTypeTest(SessionFactoryScope scope) { + final String sql = "select id, gender, ordinal_gender from EntityOfBasics"; + + // first, without explicit typing scope.inTransaction( session -> { - final String sql = "select gender, first, last, id from contacts"; final NativeQueryImplementor query = session.createNativeQuery( sql ); - // notice the reverse order from the select clause query.addScalar( "id" ); - query.addScalar( "last" ); - query.addScalar( "first" ); - query.addScalar( "gender", Contact.Gender.class ); + query.addScalar( "gender" ); + query.addScalar( "ordinal_gender" ); final List results = query.list(); assertThat( results.size(), is( 1 ) ); + final Object result = results.get( 0 ); assertThat( result, instanceOf( Object[].class ) ); final Object[] values = (Object[]) result; - assertThat( values.length, is(4 ) ); + assertThat( values.length, is(3 ) ); - assertThat( values[0], is( 1 ) ); - assertThat( values[1], is( "Contact" ) ); - assertThat( values[2], is( "My First" ) ); - assertThat( values[3], is( Contact.Gender.OTHER ) ); + assertThat( values[ 0 ], is( 1 ) ); + assertThat( values[ 1 ], is( "MALE" ) ); + assertThat( values[ 2 ], matchesOrdinal( EntityOfBasics.Gender.FEMALE ) ); + } + ); + + // then using explicit typing + scope.inTransaction( + session -> { + final NativeQueryImplementor query = session.createNativeQuery( sql ); + query.addScalar( "id" ); + query.addScalar( "gender", EntityOfBasics.Gender.class ); + query.addScalar( "ordinal_gender", EntityOfBasics.Gender.class ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final Object result = results.get( 0 ); + assertThat( result, instanceOf( Object[].class ) ); + + final Object[] values = (Object[]) result; + assertThat( values.length, is(3 ) ); + + assertThat( values[ 0 ], is( 1 ) ); + assertThat( values[ 1 ], is( EntityOfBasics.Gender.MALE ) ); + assertThat( values[ 2 ], is( EntityOfBasics.Gender.FEMALE ) ); + } + ); + } + @Test + public void explicitConversionTest(SessionFactoryScope scope) { + final String sql = "select converted_gender from EntityOfBasics"; + + // Control + scope.inTransaction( + session -> { + final NativeQueryImplementor query = session.createNativeQuery( sql ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final Object result = results.get( 0 ); + assertThat( result, instanceOf( String.class ) ); + + assertThat( result, is( "O" ) ); + } + ); + + + // Converter instance + scope.inTransaction( + session -> { + final NativeQueryImplementor query = session.createNativeQuery( sql ); + query.addScalar( + "converted_gender", + Character.class, + new EntityOfBasics.GenderConverter() + ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final Object result = results.get( 0 ); + assertThat( result, instanceOf( EntityOfBasics.Gender.class ) ); + + assertThat( result, is( EntityOfBasics.Gender.OTHER ) ); + } + ); + + // Converter class + scope.inTransaction( + session -> { + final NativeQueryImplementor query = session.createNativeQuery( sql ); + query.addScalar( + "converted_gender", + Character.class, + EntityOfBasics.GenderConverter.class + ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final Object result = results.get( 0 ); + assertThat( result, instanceOf( EntityOfBasics.Gender.class ) ); + + assertThat( result, is( EntityOfBasics.Gender.OTHER ) ); } ); } + private Matcher matchesOrdinal(Enum enumValue) { + return new CustomMatcher( "Enum ordinal value" ) { + @Override + public boolean matches(Object item) { + return ( (Number) item ).intValue() == enumValue.ordinal(); + } + }; + } + + @BeforeAll + public void verifyModel(SessionFactoryScope scope) { + final EntityMappingType entityDescriptor = scope.getSessionFactory() + .getRuntimeMetamodels() + .getEntityMappingType( EntityOfBasics.class ); + + final ModelPart part = entityDescriptor.findSubPart( "convertedGender", null ); + assertThat( part, instanceOf( BasicValuedSingularAttributeMapping.class ) ); + final BasicValuedSingularAttributeMapping attrMapping = (BasicValuedSingularAttributeMapping) part; + + assertThat( attrMapping.getJavaTypeDescriptor().getJavaType(), equalTo( EntityOfBasics.Gender.class ) ); + + final BasicValueConverter valueConverter = attrMapping.getValueConverter(); + assertThat( valueConverter, instanceOf( JpaAttributeConverter.class ) ); + assertThat( valueConverter.getDomainJavaDescriptor(), is( attrMapping.getJavaTypeDescriptor() ) ); + assertThat( valueConverter.getRelationalJavaDescriptor().getJavaType(), equalTo( Character.class ) ); + + assertThat( attrMapping.getJdbcMapping().getSqlTypeDescriptor().getJdbcTypeCode(), is( Types.CHAR ) ); + } + @BeforeEach - public void prepareData(SessionFactoryScope scope) { + public void prepareData(SessionFactoryScope scope) throws MalformedURLException { + final URL url = new URL( URL_STRING ); + scope.inTransaction( session -> { - session.persist( - new Contact( - 1, - new Contact.Name( "My First", "Contact"), - Contact.Gender.OTHER, - LocalDate.of(1990,4,18) - ) - ); + final EntityOfBasics entityOfBasics = new EntityOfBasics( 1 ); + entityOfBasics.setTheString( STRING_VALUE ); + entityOfBasics.setTheInteger( 2 ); + entityOfBasics.setGender( EntityOfBasics.Gender.MALE ); + entityOfBasics.setOrdinalGender( EntityOfBasics.Gender.FEMALE ); + entityOfBasics.setConvertedGender( EntityOfBasics.Gender.OTHER ); + entityOfBasics.setTheUrl( url ); + entityOfBasics.setTheInstant( Instant.EPOCH ); + + session.persist( entityOfBasics ); + } + ); + + scope.inTransaction( + session -> { + final EntityOfBasics entity = session.get( EntityOfBasics.class, 1 ); + assertThat( entity, notNullValue() ); } ); } @@ -130,7 +270,7 @@ public class NativeQueryScalarTests { @AfterEach public void cleanUpData(SessionFactoryScope scope) { scope.inTransaction( - session -> session.createQuery( "delete Contact" ).executeUpdate() + session -> session.createQuery( "delete EntityOfBasics" ).executeUpdate() ); } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java index 6f392309b5..4c279ac6c4 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -8,6 +8,7 @@ package org.hibernate.testing.orm.domain.gambit; import java.net.URL; import java.sql.Clob; +import java.sql.Types; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -26,6 +27,8 @@ import javax.persistence.Id; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.hibernate.annotations.SqlTypeCode; + /** * @author Steve Ebersole */ @@ -34,7 +37,8 @@ public class EntityOfBasics { public enum Gender { MALE, - FEMALE + FEMALE, + OTHER } private Integer id; @@ -60,6 +64,13 @@ public class EntityOfBasics { private ZonedDateTime theZonedDateTime; private OffsetDateTime theOffsetDateTime; + public EntityOfBasics() { + } + + public EntityOfBasics(Integer id) { + this.id = id; + } + @Id public Integer getId() { return id; @@ -127,7 +138,8 @@ public class EntityOfBasics { } @Convert( converter = GenderConverter.class ) - @Column(name = "converted_gender") + @Column(name = "converted_gender", length = 1) + @SqlTypeCode( Types.CHAR ) public Gender getConvertedGender() { return convertedGender; } @@ -244,6 +256,10 @@ public class EntityOfBasics { return null; } + if ( attribute == Gender.OTHER ) { + return 'O'; + } + if ( attribute == Gender.MALE ) { return 'M'; } @@ -257,6 +273,10 @@ public class EntityOfBasics { return null; } + if ( 'O' == dbData ) { + return Gender.OTHER; + } + if ( 'M' == dbData ) { return Gender.MALE; }