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(), sqlSelection.getValuesArrayPosition(),
resultVariable, resultVariable,
getMappedTypeDescriptor().getMappedJavaTypeDescriptor(), getMappedTypeDescriptor().getMappedJavaTypeDescriptor(),
valueConverter valueConverter,
navigablePath
); );
} }

View File

@ -67,4 +67,11 @@ public class BasicResultAssembler<J> implements DomainResultAssembler<J> {
public JavaTypeDescriptor<J> getAssembledJavaTypeDescriptor() { public JavaTypeDescriptor<J> getAssembledJavaTypeDescriptor() {
return assembledJavaTypeDescriptor; 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 java.util.function.Consumer;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; 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.AssemblerCreationState;
import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.Initializer; import org.hibernate.sql.results.spi.Initializer;
@ -22,15 +23,27 @@ public class ScalarDomainResultImpl<T> implements ScalarDomainResult<T> {
private final String resultVariable; private final String resultVariable;
private final JavaTypeDescriptor<T> javaTypeDescriptor; private final JavaTypeDescriptor<T> javaTypeDescriptor;
private final NavigablePath navigablePath;
private final DomainResultAssembler<T> assembler; private final DomainResultAssembler<T> assembler;
public ScalarDomainResultImpl( public ScalarDomainResultImpl(
int jdbcValuesArrayPosition, int jdbcValuesArrayPosition,
String resultVariable, String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor) { JavaTypeDescriptor<T> javaTypeDescriptor) {
this( jdbcValuesArrayPosition, resultVariable, javaTypeDescriptor, (NavigablePath) null );
}
public ScalarDomainResultImpl(
int jdbcValuesArrayPosition,
String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor,
NavigablePath navigablePath) {
this.resultVariable = resultVariable; this.resultVariable = resultVariable;
this.javaTypeDescriptor = javaTypeDescriptor; this.javaTypeDescriptor = javaTypeDescriptor;
this.navigablePath = navigablePath;
this.assembler = new BasicResultAssembler<>( jdbcValuesArrayPosition, javaTypeDescriptor ); this.assembler = new BasicResultAssembler<>( jdbcValuesArrayPosition, javaTypeDescriptor );
} }
@ -39,8 +52,18 @@ public class ScalarDomainResultImpl<T> implements ScalarDomainResult<T> {
String resultVariable, String resultVariable,
JavaTypeDescriptor<T> javaTypeDescriptor, JavaTypeDescriptor<T> javaTypeDescriptor,
BasicValueConverter<T,?> valueConverter) { 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.resultVariable = resultVariable;
this.javaTypeDescriptor = javaTypeDescriptor; this.javaTypeDescriptor = javaTypeDescriptor;
this.navigablePath = navigablePath;
this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaTypeDescriptor, valueConverter ); this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaTypeDescriptor, valueConverter );
} }
@ -55,6 +78,11 @@ public class ScalarDomainResultImpl<T> implements ScalarDomainResult<T> {
return javaTypeDescriptor; return javaTypeDescriptor;
} }
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override @Override
public DomainResultAssembler<T> createResultAssembler( public DomainResultAssembler<T> createResultAssembler(
Consumer<Initializer> initializerCollector, Consumer<Initializer> initializerCollector,

View File

@ -14,11 +14,9 @@ import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.Mapping; 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.type.spi.TypeConfiguration;
import org.hibernate.usertype.EnhancedUserType; import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.LoggableUserType; import org.hibernate.usertype.LoggableUserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.Sized; import org.hibernate.usertype.Sized;
import org.hibernate.usertype.UserType; import org.hibernate.usertype.UserType;
import org.hibernate.usertype.UserVersionType; import org.hibernate.usertype.UserVersionType;
@ -44,7 +41,7 @@ import org.hibernate.usertype.UserVersionType;
*/ */
public class CustomType public class CustomType
extends AbstractType extends AbstractType
implements IdentifierType, DiscriminatorType, VersionType, BasicType, StringRepresentableType, ProcedureParameterNamedBinder, ProcedureParameterExtractionAware { implements BasicType, IdentifierType, DiscriminatorType, VersionType, StringRepresentableType, ProcedureParameterNamedBinder, ProcedureParameterExtractionAware {
private final UserType userType; private final UserType userType;
private final String[] registrationKeys; private final String[] registrationKeys;
@ -90,8 +87,8 @@ public class CustomType
} }
@Override @Override
public String[] getRegistrationKeys() { public SqlTypeDescriptor getSqlTypeDescriptor() {
return registrationKeys; return sqlTypeDescriptor;
} }
@Override @Override
@ -99,6 +96,11 @@ public class CustomType
return new int[] { sqlTypeDescriptor.getSqlType() }; return new int[] { sqlTypeDescriptor.getSqlType() };
} }
@Override
public String[] getRegistrationKeys() {
return registrationKeys;
}
@Override @Override
public Size[] dictatedSizes(Mapping mapping) throws MappingException { public Size[] dictatedSizes(Mapping mapping) throws MappingException {
return new Size[] {dictatedSize}; return new Size[] {dictatedSize};
@ -271,13 +273,13 @@ public class CustomType
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public String toString(Object value) throws HibernateException { public String toString(Object value) throws HibernateException {
if ( StringRepresentableType.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof StringRepresentableType ) {
return ( (StringRepresentableType) getUserType() ).toString( value ); return ( (StringRepresentableType) getUserType() ).toString( value );
} }
if ( value == null ) { if ( value == null ) {
return null; return null;
} }
if ( EnhancedUserType.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof EnhancedUserType ) {
//noinspection deprecation //noinspection deprecation
return ( (EnhancedUserType) getUserType() ).toXMLString( value ); return ( (EnhancedUserType) getUserType() ).toXMLString( value );
} }
@ -286,10 +288,10 @@ public class CustomType
@Override @Override
public Object fromStringValue(String string) throws HibernateException { public Object fromStringValue(String string) throws HibernateException {
if ( StringRepresentableType.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof StringRepresentableType ) {
return ( (StringRepresentableType) getUserType() ).fromStringValue( string ); return ( (StringRepresentableType) getUserType() ).fromStringValue( string );
} }
if ( EnhancedUserType.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof EnhancedUserType ) {
//noinspection deprecation //noinspection deprecation
return ( (EnhancedUserType) getUserType() ).fromXMLString( string ); return ( (EnhancedUserType) getUserType() ).fromXMLString( string );
} }
@ -305,7 +307,7 @@ public class CustomType
@Override @Override
public boolean canDoSetting() { public boolean canDoSetting() {
if ( ProcedureParameterNamedBinder.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof ProcedureParameterNamedBinder ) {
return ((ProcedureParameterNamedBinder) getUserType() ).canDoSetting(); return ((ProcedureParameterNamedBinder) getUserType() ).canDoSetting();
} }
return false; return false;
@ -326,17 +328,12 @@ public class CustomType
@Override @Override
public boolean canDoExtraction() { public boolean canDoExtraction() {
if ( ProcedureParameterExtractionAware.class.isInstance( getUserType() ) ) { if ( getUserType() instanceof ProcedureParameterExtractionAware ) {
return ((ProcedureParameterExtractionAware) getUserType() ).canDoExtraction(); return ((ProcedureParameterExtractionAware) getUserType() ).canDoExtraction();
} }
return false; return false;
} }
@Override
public SqlTypeDescriptor getSqlTypeDescriptor() {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override @Override
public Object extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException { public Object extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException {
if ( canDoExtraction() ) { if ( canDoExtraction() ) {

View File

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

View File

@ -7,19 +7,34 @@
package org.hibernate.orm.test.sql.ast; package org.hibernate.orm.test.sql.ast;
import org.hibernate.cfg.AvailableSettings; 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.hql.spi.HqlQueryImplementor;
import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.internal.QuerySqmImpl;
import org.hibernate.query.sqm.sql.internal.SqmSelectInterpretation; import org.hibernate.query.sqm.sql.internal.SqmSelectInterpretation;
import org.hibernate.query.sqm.sql.internal.SqmSelectToSqlAstConverter; import org.hibernate.query.sqm.sql.internal.SqmSelectToSqlAstConverter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement; 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.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.FromClause;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SelectStatement; 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.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry; 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.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test; 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.is;
import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -47,7 +64,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
@SessionFactory @SessionFactory
public class SmokeTests { public class SmokeTests {
@Test @Test
public void testSimpleHql(SessionFactoryScope scope) { public void testSimpleHqlInterpretation(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final QueryImplementor<String> query = session.createQuery( "select e.name from SimpleEntity e", String.class ); 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 SqmSelectInterpretation sqmInterpretation = sqmConverter.interpret( sqmStatement );
final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); final SelectStatement sqlAst = sqmInterpretation.getSqlAst();
checkSqmInterpretation( sqlAst ); final FromClause fromClause = sqlAst.getQuerySpec().getFromClause();
assertThat( fromClause.getRoots().size(), is( 1 ) );
final JdbcSelect jdbcSelectOperation = SqlAstSelectToJdbcSelectConverter.interpret( final TableGroup rootTableGroup = fromClause.getRoots().get( 0 );
sqlAst, assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() );
session.getSessionFactory() 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 @Test
public void testSimpleHqlExecution(SessionFactoryScope scope) { public void testSimpleHqlExecution(SessionFactoryScope scope) {
scope.inTransaction( 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 SqmSelectToSqlAstConverter sqmConverter = new SqmSelectToSqlAstConverter(
final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); hqlQuery.getQueryOptions(),
assertThat( fromClause.getRoots().size(), is( 1 ) ); ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(),
query.getParameterBindings(),
session.getLoadQueryInfluencers(),
scope.getSessionFactory()
);
final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); final SqmSelectInterpretation sqmInterpretation = sqmConverter.interpret( sqmStatement );
assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); final SelectStatement sqlAst = sqmInterpretation.getSqlAst();
assertThat( rootTableGroup.getPrimaryTableReference().getTableExpression(), is( "mapping_simple_entity" ) );
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 // `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` // the query the base becomes `s1`. The primary table reference is always suffixed as `_0`
assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) ); assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) );
final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause(); final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause();
assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ; assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ;
final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 );
assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 );
assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) );
assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); 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();
}
);
} }
} }