Initial working support for building and executing JdbcSelect operation from simple HQL for a converted (enum) value

This commit is contained in:
Steve Ebersole 2019-09-05 06:50:29 -05:00 committed by Andrea Boriero
parent dbd108e0b7
commit 5b1df3c6c9
6 changed files with 180 additions and 44 deletions

View File

@ -104,7 +104,8 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu
sqlSelection.getValuesArrayPosition(),
resultVariable,
getMappedTypeDescriptor().getMappedJavaTypeDescriptor(),
valueConverter
valueConverter,
navigablePath
);
}

View File

@ -67,4 +67,11 @@ public class BasicResultAssembler<J> implements DomainResultAssembler<J> {
public JavaTypeDescriptor<J> getAssembledJavaTypeDescriptor() {
return assembledJavaTypeDescriptor;
}
/**
* Exposed for testing purposes
*/
public BasicValueConverter<J, ?> getValueConverter() {
return valueConverter;
}
}

View File

@ -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<T> implements ScalarDomainResult<T> {
private final String resultVariable;
private final JavaTypeDescriptor<T> javaTypeDescriptor;
private final NavigablePath navigablePath;
private final DomainResultAssembler<T> assembler;
public ScalarDomainResultImpl(
int jdbcValuesArrayPosition,
String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor) {
this( jdbcValuesArrayPosition, resultVariable, javaTypeDescriptor, (NavigablePath) null );
}
public ScalarDomainResultImpl(
int jdbcValuesArrayPosition,
String resultVariable,
JavaTypeDescriptor<T> 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<T> implements ScalarDomainResult<T> {
String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor,
BasicValueConverter<T,?> valueConverter) {
this( valuesArrayPosition, resultVariable, javaTypeDescriptor, valueConverter, null );
}
public ScalarDomainResultImpl(
int valuesArrayPosition,
String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor,
BasicValueConverter<T,?> 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<T> implements ScalarDomainResult<T> {
return javaTypeDescriptor;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public DomainResultAssembler<T> createResultAssembler(
Consumer<Initializer> initializerCollector,

View File

@ -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() ) {

View File

@ -90,6 +90,10 @@ public class EnumType<T extends Enum>
this.typeConfiguration = typeConfiguration;
}
public EnumValueConverter getEnumValueConverter() {
return enumValueConverter;
}
@Override
public void setParameterValues(Properties parameters) {
// IMPL NOTE: we handle 2 distinct cases here:

View File

@ -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<String> 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<Gender> query = session.createQuery( "select e.gender from SimpleEntity e", Gender.class );
final HqlQueryImplementor<Gender> hqlQuery = (HqlQueryImplementor<Gender>) query;
//noinspection unchecked
final SqmSelectStatement<Gender> sqmStatement = (SqmSelectStatement<Gender>) 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<Gender> query = session.createQuery( "select e.gender from SimpleEntity e", Gender.class );
query.list();
}
);
}
}