diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java index f7324815d4..d06cd6c3e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java @@ -104,7 +104,8 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu sqlSelection.getValuesArrayPosition(), resultVariable, getMappedTypeDescriptor().getMappedJavaTypeDescriptor(), - valueConverter + valueConverter, + navigablePath ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/BasicResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/BasicResultAssembler.java index 152d100380..c85f54c50a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/BasicResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/BasicResultAssembler.java @@ -67,4 +67,11 @@ public class BasicResultAssembler implements DomainResultAssembler { public JavaTypeDescriptor getAssembledJavaTypeDescriptor() { return assembledJavaTypeDescriptor; } + + /** + * Exposed for testing purposes + */ + public BasicValueConverter getValueConverter() { + return valueConverter; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ScalarDomainResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ScalarDomainResultImpl.java index 7bf2eb477e..8c5f5e6925 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ScalarDomainResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ScalarDomainResultImpl.java @@ -9,6 +9,7 @@ package org.hibernate.sql.results.internal; import java.util.function.Consumer; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.Initializer; @@ -22,15 +23,27 @@ public class ScalarDomainResultImpl implements ScalarDomainResult { private final String resultVariable; private final JavaTypeDescriptor javaTypeDescriptor; + private final NavigablePath navigablePath; + private final DomainResultAssembler assembler; public ScalarDomainResultImpl( int jdbcValuesArrayPosition, String resultVariable, JavaTypeDescriptor javaTypeDescriptor) { + this( jdbcValuesArrayPosition, resultVariable, javaTypeDescriptor, (NavigablePath) null ); + } + + public ScalarDomainResultImpl( + int jdbcValuesArrayPosition, + String resultVariable, + JavaTypeDescriptor javaTypeDescriptor, + NavigablePath navigablePath) { this.resultVariable = resultVariable; this.javaTypeDescriptor = javaTypeDescriptor; + this.navigablePath = navigablePath; + this.assembler = new BasicResultAssembler<>( jdbcValuesArrayPosition, javaTypeDescriptor ); } @@ -39,8 +52,18 @@ public class ScalarDomainResultImpl implements ScalarDomainResult { String resultVariable, JavaTypeDescriptor javaTypeDescriptor, BasicValueConverter valueConverter) { + this( valuesArrayPosition, resultVariable, javaTypeDescriptor, valueConverter, null ); + } + + public ScalarDomainResultImpl( + int valuesArrayPosition, + String resultVariable, + JavaTypeDescriptor javaTypeDescriptor, + BasicValueConverter valueConverter, + NavigablePath navigablePath) { this.resultVariable = resultVariable; this.javaTypeDescriptor = javaTypeDescriptor; + this.navigablePath = navigablePath; this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaTypeDescriptor, valueConverter ); } @@ -55,6 +78,11 @@ public class ScalarDomainResultImpl implements ScalarDomainResult { return javaTypeDescriptor; } + @Override + public NavigablePath getNavigablePath() { + return navigablePath; + } + @Override public DomainResultAssembler createResultAssembler( Consumer initializerCollector, diff --git a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java index 92720c4ed8..31934e682d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java @@ -14,11 +14,9 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Comparator; import java.util.Map; -import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.Mapping; @@ -30,7 +28,6 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.EnhancedUserType; import org.hibernate.usertype.LoggableUserType; -import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.Sized; import org.hibernate.usertype.UserType; import org.hibernate.usertype.UserVersionType; @@ -44,7 +41,7 @@ import org.hibernate.usertype.UserVersionType; */ public class CustomType extends AbstractType - implements IdentifierType, DiscriminatorType, VersionType, BasicType, StringRepresentableType, ProcedureParameterNamedBinder, ProcedureParameterExtractionAware { + implements BasicType, IdentifierType, DiscriminatorType, VersionType, StringRepresentableType, ProcedureParameterNamedBinder, ProcedureParameterExtractionAware { private final UserType userType; private final String[] registrationKeys; @@ -90,8 +87,8 @@ public class CustomType } @Override - public String[] getRegistrationKeys() { - return registrationKeys; + public SqlTypeDescriptor getSqlTypeDescriptor() { + return sqlTypeDescriptor; } @Override @@ -99,6 +96,11 @@ public class CustomType return new int[] { sqlTypeDescriptor.getSqlType() }; } + @Override + public String[] getRegistrationKeys() { + return registrationKeys; + } + @Override public Size[] dictatedSizes(Mapping mapping) throws MappingException { return new Size[] {dictatedSize}; @@ -271,13 +273,13 @@ public class CustomType @Override @SuppressWarnings("unchecked") public String toString(Object value) throws HibernateException { - if ( StringRepresentableType.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof StringRepresentableType ) { return ( (StringRepresentableType) getUserType() ).toString( value ); } if ( value == null ) { return null; } - if ( EnhancedUserType.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof EnhancedUserType ) { //noinspection deprecation return ( (EnhancedUserType) getUserType() ).toXMLString( value ); } @@ -286,10 +288,10 @@ public class CustomType @Override public Object fromStringValue(String string) throws HibernateException { - if ( StringRepresentableType.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof StringRepresentableType ) { return ( (StringRepresentableType) getUserType() ).fromStringValue( string ); } - if ( EnhancedUserType.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof EnhancedUserType ) { //noinspection deprecation return ( (EnhancedUserType) getUserType() ).fromXMLString( string ); } @@ -305,7 +307,7 @@ public class CustomType @Override public boolean canDoSetting() { - if ( ProcedureParameterNamedBinder.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof ProcedureParameterNamedBinder ) { return ((ProcedureParameterNamedBinder) getUserType() ).canDoSetting(); } return false; @@ -326,17 +328,12 @@ public class CustomType @Override public boolean canDoExtraction() { - if ( ProcedureParameterExtractionAware.class.isInstance( getUserType() ) ) { + if ( getUserType() instanceof ProcedureParameterExtractionAware ) { return ((ProcedureParameterExtractionAware) getUserType() ).canDoExtraction(); } return false; } - @Override - public SqlTypeDescriptor getSqlTypeDescriptor() { - throw new NotYetImplementedFor6Exception( getClass() ); - } - @Override public Object extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException { if ( canDoExtraction() ) { 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 d27e0f5d51..c05161beca 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -90,6 +90,10 @@ public class EnumType this.typeConfiguration = typeConfiguration; } + public EnumValueConverter getEnumValueConverter() { + return enumValueConverter; + } + @Override public void setParameterValues(Properties parameters) { // IMPL NOTE: we handle 2 distinct cases here: diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java index 535b0e687b..9b121b1050 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java @@ -7,19 +7,34 @@ package org.hibernate.orm.test.sql.ast; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping; +import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; +import org.hibernate.orm.test.metamodel.mapping.SmokeTests.Gender; +import org.hibernate.query.NavigablePath; import org.hibernate.query.hql.spi.HqlQueryImplementor; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.sql.internal.SqmSelectInterpretation; import org.hibernate.query.sqm.sql.internal.SqmSelectToSqlAstConverter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.sql.ast.spi.SqlAstSelectToJdbcSelectConverter; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.results.internal.BasicResultAssembler; +import org.hibernate.sql.results.internal.ScalarDomainResultImpl; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.type.CustomType; +import org.hibernate.type.EnumType; +import org.hibernate.usertype.UserType; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -27,6 +42,8 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; +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; @@ -47,7 +64,7 @@ import static org.hamcrest.MatcherAssert.assertThat; @SessionFactory public class SmokeTests { @Test - public void testSimpleHql(SessionFactoryScope scope) { + public void testSimpleHqlInterpretation(SessionFactoryScope scope) { scope.inTransaction( session -> { final QueryImplementor query = session.createQuery( "select e.name from SimpleEntity e", String.class ); @@ -66,17 +83,32 @@ public class SmokeTests { final SqmSelectInterpretation sqmInterpretation = sqmConverter.interpret( sqmStatement ); final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); - checkSqmInterpretation( sqlAst ); + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots().size(), is( 1 ) ); - final JdbcSelect jdbcSelectOperation = SqlAstSelectToJdbcSelectConverter.interpret( - sqlAst, - session.getSessionFactory() - ); + final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); + assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); + assertThat( rootTableGroup.getPrimaryTableReference().getTableExpression(), is( "mapping_simple_entity" ) ); - assertThat( jdbcSelectOperation.getSql(), is( "select s1_0.name from mapping_simple_entity as s1_0" ) ); + assertThat( rootTableGroup.getTableReferenceJoins().size(), is( 0 ) ); + + assertThat( rootTableGroup.hasTableGroupJoins(), is( false ) ); + + + // `s` is the "alias stem" for `SimpleEntity` and as it is the first entity with that stem in + // the query the base becomes `s1`. The primary table reference is always suffixed as `_0` + assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) ); + + final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause(); + assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ; + final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 ); + assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); + assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); + assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); } ); } + @Test public void testSimpleHqlExecution(SessionFactoryScope scope) { scope.inTransaction( @@ -86,29 +118,96 @@ public class SmokeTests { } ); } + @Test + public void testConvertedHqlInterpretation(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( "select e.gender from SimpleEntity e", Gender.class ); + final HqlQueryImplementor hqlQuery = (HqlQueryImplementor) query; + //noinspection unchecked + final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); - private void checkSqmInterpretation(SelectStatement sqlAst) { - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots().size(), is( 1 ) ); + final SqmSelectToSqlAstConverter sqmConverter = new SqmSelectToSqlAstConverter( + hqlQuery.getQueryOptions(), + ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), + query.getParameterBindings(), + session.getLoadQueryInfluencers(), + scope.getSessionFactory() + ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); - assertThat( rootTableGroup.getPrimaryTableReference().getTableExpression(), is( "mapping_simple_entity" ) ); + final SqmSelectInterpretation sqmInterpretation = sqmConverter.interpret( sqmStatement ); + final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); - assertThat( rootTableGroup.getTableReferenceJoins().size(), is( 0 ) ); + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots().size(), is( 1 ) ); - assertThat( rootTableGroup.hasTableGroupJoins(), is( false ) ); + final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); + assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); + assertThat( rootTableGroup.getPrimaryTableReference().getTableExpression(), is( "mapping_simple_entity" ) ); + + assertThat( rootTableGroup.getTableReferenceJoins().size(), is( 0 ) ); + + assertThat( rootTableGroup.hasTableGroupJoins(), is( false ) ); - // `s` is the "alias stem" for `SimpleEntity` and as it is the first entity with that stem in - // the query the base becomes `s1`. The primary table reference is always suffixed as `_0` - assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) ); + // `s` is the "alias stem" for `SimpleEntity` and as it is the first entity with that stem in + // the query the base becomes `s1`. The primary table reference is always suffixed as `_0` + assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) ); - final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause(); - assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ; - final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 ); - assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); - assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); - assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); + final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause(); + assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ; + + final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 ); + assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); + assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); + assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); + + assertThat( sqlSelection, instanceOf( SqlSelectionImpl.class ) ); + final Expression selectedExpression = ( (SqlSelectionImpl) sqlSelection ).getWrappedSqlExpression(); + assertThat( selectedExpression, instanceOf( ColumnReference.class ) ); + final ColumnReference columnReference = (ColumnReference) selectedExpression; + assertThat( columnReference.getReferencedColumnExpression(), is( "gender" ) ); + assertThat( columnReference.renderSqlFragment( scope.getSessionFactory() ), is( "s1_0.gender" ) ); + + final MappingModelExpressable selectedExpressable = selectedExpression.getExpressionType(); + assertThat( selectedExpressable, instanceOf( CustomType.class ) ); + final UserType userType = ( (CustomType) selectedExpressable ).getUserType(); + assertThat( userType, instanceOf( EnumType.class ) ); + final EnumValueConverter enumValueConverter = ( (EnumType) userType ).getEnumValueConverter(); + assertThat( enumValueConverter, notNullValue() ); + assertThat( enumValueConverter.getDomainJavaDescriptor().getJavaType(), equalTo( Gender.class ) ); + + assertThat( sqlAst.getDomainResultDescriptors().size(), is( 1 ) ); + final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); + final NavigablePath expectedSelectedPath = new NavigablePath( + org.hibernate.orm.test.metamodel.mapping.SmokeTests.SimpleEntity.class.getName(), + "e" + ).append( "gender" ); + assertThat( domainResult.getNavigablePath(), equalTo( expectedSelectedPath ) ); + assertThat( domainResult, instanceOf( ScalarDomainResultImpl.class ) ); + + // ScalarDomainResultImpl creates and caches the assembler at its creation. + // this just gets access to that cached one + final DomainResultAssembler resultAssembler = domainResult.createResultAssembler( + null, + null + ); + + assertThat( resultAssembler, instanceOf( BasicResultAssembler.class ) ); + final BasicValueConverter valueConverter = ( (BasicResultAssembler) resultAssembler ).getValueConverter(); + assertThat( valueConverter, notNullValue() ); + assertThat( valueConverter, instanceOf( OrdinalEnumValueConverter.class ) ); + } + ); + } + + @Test + public void testConvertedHqlExecution(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( "select e.gender from SimpleEntity e", Gender.class ); + query.list(); + } + ); } }