From 26b08fd35ef64dcaaea008f9489be347e04395db Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 19 Sep 2019 15:59:59 -0500 Subject: [PATCH] initial working literal and parameter selection --- .../query/sqm/internal/QuerySqmImpl.java | 44 +++++++++++++----- .../sqm/sql/BaseSqmToSqlAstConverter.java | 22 ++++++++- .../query/sqm/sql/SqlAstCreationState.java | 6 +++ .../internal/SqmParameterInterpretation.java | 46 ++++++++++++++++++- .../ast/tree/expression/AbstractLiteral.java | 2 +- .../exec/internal/AbstractJdbcParameter.java | 28 ++++++++++- .../orm/test/sql/exec/SmokeTests.java | 44 +++++++++++++++++- 7 files changed, 176 insertions(+), 16 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index c3070cd484..99042206c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -44,11 +44,13 @@ import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; +import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.mutation.spi.DeleteHandler; import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.tree.SqmDmlStatement; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; @@ -199,20 +201,40 @@ else if ( Tuple.class.isAssignableFrom( resultClass ) ) { final SqmSelection sqmSelection = selections.get( 0 ); - if ( ! resultClass.isAssignableFrom( sqmSelection.getNodeType().getExpressableJavaTypeDescriptor().getJavaType() ) ) { - final String errorMessage = String.format( - "Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array", - resultClass.getName(), - sqmSelection.getNodeType().getExpressableJavaTypeDescriptor().getJavaType().getName() - ); + if ( sqmSelection.getSelectableNode() instanceof SqmParameter ) { + final SqmParameter sqmParameter = (SqmParameter) sqmSelection.getSelectableNode(); - if ( sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) { - throw new IllegalArgumentException( errorMessage ); - } - else { - throw new QueryTypeMismatchException( errorMessage ); + // we may not yet know a selection type + if ( sqmParameter.getNodeType() == null || sqmParameter.getNodeType().getExpressableJavaTypeDescriptor() == null ) { + // we can't verify the result type up front + return; } } + + verifyResultType( resultClass, sqmSelection.getNodeType(), sessionFactory ); + } + } + + private static void verifyResultType( + Class resultClass, + SqmExpressable sqmExpressable, + SessionFactoryImplementor sessionFactory) { + assert sqmExpressable != null; + assert sqmExpressable.getExpressableJavaTypeDescriptor() != null; + + if ( ! resultClass.isAssignableFrom( sqmExpressable.getExpressableJavaTypeDescriptor().getJavaType() ) ) { + final String errorMessage = String.format( + "Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array", + resultClass.getName(), + sqmExpressable.getExpressableJavaTypeDescriptor().getJavaType().getName() + ); + + if ( sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) { + throw new IllegalArgumentException( errorMessage ); + } + else { + throw new QueryTypeMismatchException( errorMessage ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index d7dedc57f8..9a2375ff15 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -220,6 +220,16 @@ public SqlAliasBaseGenerator getSqlAliasBaseGenerator() { return sqlAliasBaseManager; } + @Override + public DomainParameterXref getDomainParameterXref() { + return domainParameterXref; + } + + @Override + public QueryParameterBindings getDomainParameterBindings() { + return domainParameterBindings; + } + @Override public LockMode determineLockMode(String identificationVariable) { return queryOptions.getLockOptions().getEffectiveLockMode( identificationVariable ); @@ -653,7 +663,17 @@ private Expression consumeSqmParameter(SqmParameter sqmParameter) { } protected MappingModelExpressable determineValueMapping(SqmExpression sqmExpression) { - final SqmExpressable nodeType = sqmExpression.getNodeType(); + SqmExpressable nodeType = sqmExpression.getNodeType(); + + if ( nodeType == null ) { + if ( sqmExpression instanceof SqmParameter ) { + final SqmParameter sqmParameter = (SqmParameter) sqmExpression; + final QueryParameterBinding binding = domainParameterBindings.getBinding( domainParameterXref.getQueryParameter( sqmParameter ) ); + assert binding != null; + + nodeType = binding.getBindType(); + } + } MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( nodeType ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqlAstCreationState.java index ed530d18bc..0487bfd72a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqlAstCreationState.java @@ -9,6 +9,8 @@ import java.util.List; import org.hibernate.LockMode; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; @@ -32,6 +34,10 @@ public interface SqlAstCreationState { SqlAliasBaseGenerator getSqlAliasBaseGenerator(); + DomainParameterXref getDomainParameterXref(); + + QueryParameterBindings getDomainParameterBindings(); + LockMode determineLockMode(String identificationVariable); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java index bacb710c42..28bc76a245 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java @@ -9,16 +9,24 @@ import java.util.List; import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.query.SemanticException; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.ast.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.results.internal.domain.basic.BasicResult; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultCreationState; /** * @author Steve Ebersole */ -public class SqmParameterInterpretation implements Expression { +public class SqmParameterInterpretation implements Expression, DomainResultProducer { private final SqmParameter sqmParameter; private final MappingModelExpressable valueMapping; @@ -48,4 +56,40 @@ public void accept(SqlAstWalker sqlTreeWalker) { public MappingModelExpressable getExpressionType() { return valueMapping; } + + @Override + public DomainResult createDomainResult( + String resultVariable, + DomainResultCreationState creationState) { + if ( resolvedExpression instanceof SqlTuple ) { + throw new SemanticException( "Composite query parameter cannot be used in select" ); + } + + AllowableParameterType nodeType = sqmParameter.getNodeType(); + if ( nodeType == null ) { + final QueryParameterImplementor queryParameter = creationState.getSqlAstCreationState() + .getDomainParameterXref() + .getQueryParameter( sqmParameter ); + final QueryParameterBinding binding = creationState.getSqlAstCreationState() + .getDomainParameterBindings() + .getBinding( queryParameter ); + nodeType = binding.getBindType(); + } + + final SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection( + resolvedExpression, + nodeType.getExpressableJavaTypeDescriptor(), + creationState.getSqlAstCreationState() + .getCreationContext() + .getSessionFactory() + .getTypeConfiguration() + ); + + //noinspection unchecked + return new BasicResult( + sqlSelection.getValuesArrayPosition(), + resultVariable, + nodeType.getExpressableJavaTypeDescriptor() + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/AbstractLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/AbstractLiteral.java index 077df37963..5893bbfbf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/AbstractLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/AbstractLiteral.java @@ -76,7 +76,7 @@ public DomainResult createDomainResult( //noinspection unchecked return new BasicResult<>( - sqlSelection.getJdbcResultSetIndex(), + sqlSelection.getValuesArrayPosition(), resultVariable, type.getMappedTypeDescriptor().getMappedJavaTypeDescriptor() ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java index 1fa0311247..c612e55ff8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java @@ -13,13 +13,16 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.mapping.SqlExpressable; import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.exec.ExecutionException; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -28,7 +31,7 @@ * @author Steve Ebersole */ public abstract class AbstractJdbcParameter - implements JdbcParameter, JdbcParameterBinder, MappingModelExpressable { + implements JdbcParameter, JdbcParameterBinder, MappingModelExpressable, SqlExpressable { private final JdbcMapping jdbcMapping; @@ -41,6 +44,29 @@ public JdbcParameterBinder getParameterBinder() { return this; } + @Override + public JdbcMapping getJdbcMapping() { + return jdbcMapping; + } + + @Override + public SqlSelection createSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { + // todo (6.0) : investigate "virtual" or "static" selections + // - anything that is the same for each row always - parameter, literal, etc; + // the idea would be to write the value directly into the JdbcValues array + // and not generating a SQL selection in the query sent to DB + return new SqlSelectionImpl( + jdbcPosition, + valuesArrayPosition, + this, + jdbcMapping + ); + } + @Override public void bindParameterValue( PreparedStatement statement, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java index 3518b4fe1a..11e4c7e2e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java @@ -160,6 +160,28 @@ public void testHqlSelectEmbeddableSubAttribute(SessionFactoryScope scope) { ); } + @Test + public void testHqlSelectLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( "select 'items' from SimpleEntity e", String.class ); + final String attribute1 = query.uniqueResult(); + assertThat( attribute1, is( "items" ) ); + } + ); + } + + @Test + public void testHqlSelectParameter(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( "select :param from SimpleEntity e", String.class ); + final String attribute1 = query.setParameter( "param", "items" ).uniqueResult(); + assertThat( attribute1, is( "items" ) ); + } + ); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Dynamic instantiations @@ -254,6 +276,26 @@ public void testHqlNestedDynamicInstantiation(SessionFactoryScope scope) { ); } + @Test + public void testHqlNestedDynamicInstantiationWithLiteral(SessionFactoryScope scope) { + scope.getSessionFactory().inTransaction( + session -> { + final Query query = session.createQuery( + "select new CategorizedListItemDto( new ListItemDto( 'items', e.component.attribute1 ), e.component.attribute2, e.name ) from SimpleEntity e", + CategorizedListItemDto.class + ); + + final CategorizedListItemDto dto = query.getSingleResult(); + assertThat( dto, notNullValue() ); + assertThat( dto.category, notNullValue() ); + assertThat( dto.category.code, is( "items") ); + assertThat( dto.category.value, is( "a1") ); + assertThat( dto.code, is( "a2" ) ); + assertThat( dto.value, is( "Fab" ) ); + } + ); + } + @Test public void testHqlMultipleDynamicInstantiation(SessionFactoryScope scope) { scope.getSessionFactory().inTransaction( @@ -278,7 +320,7 @@ public void testHqlMultipleDynamicInstantiation(SessionFactoryScope scope) { } @Test - public void testBasicSetterDynamicInstantiation(SessionFactoryScope scope) { + public void testHqlBasicSetterDynamicInstantiation(SessionFactoryScope scope) { scope.getSessionFactory().inTransaction( session -> { final Query query = session.createQuery(