From bbe17b940a47337423a93d5a7d384a218182db85 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 27 Feb 2023 09:39:51 -0600 Subject: [PATCH] HHH-16229 - Consider pluggability for rendering "JDBC" parameters --- .../service/StandardServiceInitiators.java | 2 + .../JdbcParameterRendererInitiator.java | 33 ++++++++ .../JdbcParameterRendererStandard.java | 27 +++++++ .../sql/ast/spi/AbstractSqlAstTranslator.java | 61 +++++++++------ .../sql/ast/spi/JdbcParameterRenderer.java | 29 +++++++ .../AnsiTrimEmulationFunctionTest.java | 41 ++++++---- .../sql/ast/JdbcParameterRendererTests.java | 62 +++++++++++++++ .../orm/test/sql/ast/SmokeTests.java | 78 ++++++++----------- .../orm/test/sql/ast/SqlAstHelper.java | 40 ++++++++++ 9 files changed, 285 insertions(+), 88 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererInitiator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererStandard.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcParameterRenderer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/JdbcParameterRendererTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SqlAstHelper.java diff --git a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java index 464869ef89..1f9f297c84 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java +++ b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java @@ -38,6 +38,7 @@ import org.hibernate.query.sqm.mutation.internal.SqmMultiTableMutationStrategyPr import org.hibernate.resource.beans.spi.ManagedBeanRegistryInitiator; import org.hibernate.resource.transaction.internal.TransactionCoordinatorBuilderInitiator; import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator; +import org.hibernate.sql.ast.internal.JdbcParameterRendererInitiator; import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerProviderInitiator; import org.hibernate.tool.schema.internal.SchemaManagementToolInitiator; import org.hibernate.tool.schema.internal.script.SqlScriptExtractorInitiator; @@ -99,6 +100,7 @@ public final class StandardServiceInitiators { serviceInitiators.add( JdbcValuesMappingProducerProviderInitiator.INSTANCE ); serviceInitiators.add( SqmMultiTableMutationStrategyProviderInitiator.INSTANCE ); + serviceInitiators.add( JdbcParameterRendererInitiator.INSTANCE ); serviceInitiators.trimToSize(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererInitiator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererInitiator.java new file mode 100644 index 0000000000..a7cd7edc74 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererInitiator.java @@ -0,0 +1,33 @@ +/* + * 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.sql.ast.internal; + +import java.util.Map; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.sql.ast.spi.JdbcParameterRenderer; + +/** + * @author Steve Ebersole + */ +public class JdbcParameterRendererInitiator implements StandardServiceInitiator { + /** + * Singleton access + */ + public static final JdbcParameterRendererInitiator INSTANCE = new JdbcParameterRendererInitiator(); + + @Override + public JdbcParameterRenderer initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + return JdbcParameterRendererStandard.INSTANCE; + } + + @Override + public Class getServiceInitiated() { + return JdbcParameterRenderer.class; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererStandard.java new file mode 100644 index 0000000000..88b02578a7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/JdbcParameterRendererStandard.java @@ -0,0 +1,27 @@ +/* + * 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.sql.ast.internal; + +import org.hibernate.dialect.Dialect; +import org.hibernate.sql.ast.spi.JdbcParameterRenderer; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * @author Steve Ebersole + */ +public class JdbcParameterRendererStandard implements JdbcParameterRenderer { + /** + * Singleton access + */ + public static final JdbcParameterRendererStandard INSTANCE = new JdbcParameterRendererStandard(); + + @Override + public void renderJdbcParameter(int position, JdbcType jdbcType, SqlAppender appender, Dialect dialect) { + jdbcType.appendWriteExpression( "?", appender, dialect ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index aee1981c03..3fc3f5e837 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -95,6 +95,7 @@ import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTableGroup; import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression; import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; @@ -102,7 +103,6 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.CastTarget; import org.hibernate.sql.ast.tree.expression.Collation; import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression; import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; @@ -192,7 +192,6 @@ import org.hibernate.sql.model.internal.TableInsertStandard; import org.hibernate.sql.model.internal.TableUpdateCustomSql; import org.hibernate.sql.model.internal.TableUpdateStandard; import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider; import org.hibernate.type.BasicPluralType; @@ -268,6 +267,12 @@ public abstract class AbstractSqlAstTranslator implemen private final List parameterBinders = new ArrayList<>(); private final JdbcParametersImpl jdbcParameters = new JdbcParametersImpl(); + private JdbcParameterBindings jdbcParameterBindings; + private Map appliedParameterBindings = Collections.emptyMap(); + private SqlAstNodeRenderingMode parameterRenderingMode = SqlAstNodeRenderingMode.DEFAULT; + private final JdbcParameterRenderer jdbcParameterRenderer; + + private final Set filterJdbcParameters = new HashSet<>(); @@ -302,16 +307,19 @@ public abstract class AbstractSqlAstTranslator implemen private transient BasicType stringType; private transient BasicType booleanType; - private SqlAstNodeRenderingMode parameterRenderingMode = SqlAstNodeRenderingMode.DEFAULT; - - private Map appliedParameterBindings = Collections.emptyMap(); - private JdbcParameterBindings jdbcParameterBindings; private LockOptions lockOptions; private Limit limit; private JdbcParameter offsetParameter; private JdbcParameter limitParameter; private ForUpdateClause forUpdate; + protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + this.sessionFactory = sessionFactory; + this.dialect = sessionFactory.getJdbcServices().getDialect(); + this.statementStack.push( statement ); + this.jdbcParameterRenderer = sessionFactory.getServiceRegistry().getService( JdbcParameterRenderer.class ); + } + private static Clause matchWithClause(Clause clause) { if ( clause == Clause.WITH ) { return Clause.WITH; @@ -323,12 +331,6 @@ public abstract class AbstractSqlAstTranslator implemen return dialect; } - protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { - this.sessionFactory = sessionFactory; - this.dialect = sessionFactory.getJdbcServices().getDialect(); - this.statementStack.push( statement ); - } - @Override public SessionFactoryImplementor getSessionFactory() { return sessionFactory; @@ -499,7 +501,7 @@ public abstract class AbstractSqlAstTranslator implemen @Override public boolean supportsFilterClause() { - // By default we report false because not many dialects support this + // By default, we report false because not many dialects support this return false; } @@ -6178,27 +6180,38 @@ public abstract class AbstractSqlAstTranslator implemen public void visitParameter(JdbcParameter jdbcParameter) { switch ( getParameterRenderingMode() ) { case NO_UNTYPED: - case NO_PLAIN_PARAMETER: + case NO_PLAIN_PARAMETER: { renderCasted( jdbcParameter ); break; + } case INLINE_PARAMETERS: - case INLINE_ALL_PARAMETERS: + case INLINE_ALL_PARAMETERS: { renderExpressionAsLiteral( jdbcParameter, jdbcParameterBindings ); break; + } case DEFAULT: - default: - jdbcParameter.getExpressionType() - .getJdbcMappings() - .get( 0 ) - .getJdbcType() - .appendWriteExpression( "?", this, getDialect() ); - - parameterBinders.add( jdbcParameter.getParameterBinder() ); - jdbcParameters.addParameter( jdbcParameter ); + default: { + visitParameterAsParameter( jdbcParameter ); break; + } } } + protected void visitParameterAsParameter(JdbcParameter jdbcParameter) { + renderParameterAsParameter( jdbcParameter ); + parameterBinders.add( jdbcParameter.getParameterBinder() ); + jdbcParameters.addParameter( jdbcParameter ); + } + + protected void renderParameterAsParameter(JdbcParameter jdbcParameter) { + jdbcParameterRenderer.renderJdbcParameter( + parameterBinders.size() + 1, + jdbcParameter.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType(), + this, + getDialect() + ); + } + @Override public void render(SqlAstNode sqlAstNode, SqlAstNodeRenderingMode renderingMode) { SqlAstNodeRenderingMode original = this.parameterRenderingMode; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcParameterRenderer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcParameterRenderer.java new file mode 100644 index 0000000000..68bc109a62 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcParameterRenderer.java @@ -0,0 +1,29 @@ +/* + * 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.sql.ast.spi; + +import org.hibernate.dialect.Dialect; +import org.hibernate.service.Service; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * Extension point, intended for use from Hibernate Reactive, to render JDBC + * parameter placeholders into the SQL query string being generated. + * + * @author Steve Ebersole + */ +public interface JdbcParameterRenderer extends Service { + /** + * Render the parameter for the given position + * + * @param position The 1-based position of the parameter. + * @param jdbcType The type of the parameter + * @param appender The appender where the parameter should be rendered + * @param dialect The Dialect in use within the SessionFactory + */ + void renderJdbcParameter(int position, JdbcType jdbcType, SqlAppender appender, Dialect dialect); +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java index a327678e6d..50ffeb4a94 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java @@ -9,6 +9,7 @@ package org.hibernate.orm.test.dialect.function; import java.util.ArrayList; import java.util.List; +import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseDialect; @@ -30,90 +31,95 @@ import org.hibernate.type.descriptor.jdbc.CharJdbcType; import org.hibernate.type.internal.BasicTypeImpl; import org.hibernate.type.spi.TypeConfiguration; -import org.junit.Test; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.ServiceRegistryScope; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * TODO : javadoc * - * @author Steve Ebersole + * @author Christian Beikov */ +@ServiceRegistry public class AnsiTrimEmulationFunctionTest { private static final String trimSource = "a.column"; @Test - public void testBasicSqlServerProcessing() { + public void testBasicSqlServerProcessing(ServiceRegistryScope scope) { Dialect dialect = new SQLServerDialect(); TrimFunction function = new TrimFunction( dialect, new TypeConfiguration() ); - performBasicSpaceTrimmingTests( dialect, function ); + performBasicSpaceTrimmingTests( dialect, scope.getRegistry(), function ); final String expectedTrimPrep = "replace(replace(a.column,' ','#%#%'),'-',' ')"; final String expectedPostTrimPrefix = "replace(replace("; final String expectedPostTrimSuffix = ",' ','-'),'#%#%',' ')"; // -> trim(LEADING '-' FROM a.column) - String rendered = render( dialect, function, TrimSpec.LEADING, '-', trimSource ); + String rendered = render( dialect, scope.getRegistry(), function, TrimSpec.LEADING, '-', trimSource ); String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) - rendered = render( dialect, function, TrimSpec.TRAILING, '-', trimSource ); + rendered = render( dialect, scope.getRegistry(), function, TrimSpec.TRAILING, '-', trimSource ); expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) - rendered = render( dialect, function, TrimSpec.BOTH, '-', trimSource ); + rendered = render( dialect, scope.getRegistry(), function, TrimSpec.BOTH, '-', trimSource ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); } @Test - public void testBasicSybaseProcessing() { + public void testBasicSybaseProcessing(ServiceRegistryScope scope) { Dialect dialect = new SybaseDialect(); TrimFunction function = new TrimFunction( dialect, new TypeConfiguration() ); - performBasicSpaceTrimmingTests( dialect, function ); + performBasicSpaceTrimmingTests( dialect, scope.getRegistry(), function ); final String expectedTrimPrep = "str_replace(str_replace(a.column,' ','#%#%'),'-',' ')"; final String expectedPostTrimPrefix = "str_replace(str_replace("; final String expectedPostTrimSuffix = ",' ','-'),'#%#%',' ')"; // -> trim(LEADING '-' FROM a.column) - String rendered = render( dialect, function, TrimSpec.LEADING, '-', trimSource ); + String rendered = render( dialect, scope.getRegistry(), function, TrimSpec.LEADING, '-', trimSource ); String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) - rendered = render( dialect, function, TrimSpec.TRAILING, '-', trimSource ); + rendered = render( dialect, scope.getRegistry(), function, TrimSpec.TRAILING, '-', trimSource ); expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) - rendered = render( dialect, function, TrimSpec.BOTH, '-', trimSource ); + rendered = render( dialect, scope.getRegistry(), function, TrimSpec.BOTH, '-', trimSource ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); } - private void performBasicSpaceTrimmingTests(Dialect dialect, TrimFunction function) { + private void performBasicSpaceTrimmingTests(Dialect dialect, StandardServiceRegistry registry, TrimFunction function) { // -> trim(a.column) - String rendered = render( dialect, function, TrimSpec.BOTH, ' ', trimSource ); + String rendered = render( dialect, registry, function, TrimSpec.BOTH, ' ', trimSource ); assertEquals( "ltrim(rtrim(a.column))", rendered ); // -> trim(LEADING FROM a.column) - rendered = render( dialect, function, TrimSpec.LEADING, ' ', trimSource ); + rendered = render( dialect, registry, function, TrimSpec.LEADING, ' ', trimSource ); assertEquals( "ltrim(a.column)", rendered ); // -> trim(TRAILING FROM a.column) - rendered = render( dialect, function, TrimSpec.TRAILING, ' ', trimSource ); + rendered = render( dialect, registry, function, TrimSpec.TRAILING, ' ', trimSource ); assertEquals( "rtrim(a.column)", rendered ); } private String render( Dialect dialect, + StandardServiceRegistry registry, TrimFunction function, TrimSpec trimSpec, char trimCharacter, @@ -121,6 +127,7 @@ public class AnsiTrimEmulationFunctionTest { SessionFactoryImplementor factory = Mockito.mock( SessionFactoryImplementor.class ); JdbcServices jdbcServices = Mockito.mock( JdbcServices.class ); Mockito.doReturn( jdbcServices ).when( factory ).getJdbcServices(); + Mockito.doReturn( registry ).when( factory ).getServiceRegistry(); Mockito.doReturn( dialect ).when( jdbcServices ).getDialect(); StandardSqlAstTranslator walker = new StandardSqlAstTranslator<>( factory, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/JdbcParameterRendererTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/JdbcParameterRendererTests.java new file mode 100644 index 0000000000..778e499c5d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/JdbcParameterRendererTests.java @@ -0,0 +1,62 @@ +/* + * 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.orm.test.sql.ast; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.sql.ast.spi.JdbcParameterRenderer; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @implNote Restricted to H2 as there is nothing intrinsically Dialect specific here, + * though each database has specific syntax for labelled parameters + * + * @author Steve Ebersole + */ +@ServiceRegistry( services = @ServiceRegistry.Service( + role = JdbcParameterRenderer.class, + impl = JdbcParameterRendererTests.JdbcParameterRendererImpl.class +) ) +@DomainModel( annotatedClasses = EntityOfBasics.class ) +@SessionFactory( useCollectingStatementInspector = true ) +@RequiresDialect( H2Dialect.class ) +public class JdbcParameterRendererTests { + @Test + public void basicTest(SessionFactoryScope scope) { + final String queryString = "select e from EntityOfBasics e where e.id = :id"; + + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + session.createSelectionQuery( queryString, EntityOfBasics.class ).setParameter( "id", 1 ).list(); + } ); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + final String sql = statementInspector.getSqlQueries().get( 0 ); + assertThat( sql ).contains( "?1" ); + } + + public static class JdbcParameterRendererImpl implements JdbcParameterRenderer { + @Override + public void renderJdbcParameter(int position, JdbcType jdbcType, SqlAppender appender, Dialect dialect) { + jdbcType.appendWriteExpression( "?" + position, appender, dialect ); + } + } +} 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 e56ee27b16..09a00a4488 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 @@ -71,62 +71,46 @@ import static org.hamcrest.MatcherAssert.assertThat; public class SmokeTests { @Test public void testSimpleHqlInterpretation(SessionFactoryScope scope) { - scope.inTransaction( - session -> { - final QueryImplementor query = session.createQuery( - "select e.name from SimpleEntity e", - String.class - ); - final SqmQueryImplementor hqlQuery = (SqmQueryImplementor) query; - final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); + scope.inTransaction( (session) -> { + final SelectStatement sqlAst = SqlAstHelper.translateHqlSelectQuery( + "select e.name from SimpleEntity e", + String.class, + session + ); - final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( - sqmStatement, - hqlQuery.getQueryOptions(), - ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), - query.getParameterBindings(), - session.getLoadQueryInfluencers(), - scope.getSessionFactory(), - true - ); + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots().size(), is( 1 ) ); - final SqmTranslation sqmInterpretation = sqmConverter.translate(); - final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); + final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); + assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); + assertThat( rootTableGroup.getPrimaryTableReference().getTableId(), is( "mapping_simple_entity" ) ); - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots().size(), is( 1 ) ); + assertThat( rootTableGroup.getTableReferenceJoins().size(), is( 0 ) ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getPrimaryTableReference(), notNullValue() ); - assertThat( rootTableGroup.getPrimaryTableReference().getTableId(), is( "mapping_simple_entity" ) ); - - assertThat( rootTableGroup.getTableReferenceJoins().size(), is( 0 ) ); - - assertThat( rootTableGroup.getTableGroupJoins().isEmpty(), is( true ) ); + assertThat( rootTableGroup.getTableGroupJoins().isEmpty(), is( true ) ); - // `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() ); - final JdbcOperationQuerySelect jdbcSelectOperation = new StandardSqlAstTranslator( - session.getSessionFactory(), - sqlAst - ).translate( null, QueryOptions.NONE ); + final JdbcOperationQuerySelect jdbcSelectOperation = new StandardSqlAstTranslator( + session.getSessionFactory(), + sqlAst + ).translate( null, QueryOptions.NONE ); - assertThat( - jdbcSelectOperation.getSqlString(), - is( "select s1_0.name from mapping_simple_entity s1_0" ) - ); - } - ); + assertThat( + jdbcSelectOperation.getSqlString(), + is( "select s1_0.name from mapping_simple_entity s1_0" ) + ); + } ); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SqlAstHelper.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SqlAstHelper.java new file mode 100644 index 0000000000..f3fbdde714 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SqlAstHelper.java @@ -0,0 +1,40 @@ +/* + * 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.orm.test.sql.ast; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.query.hql.spi.SqmQueryImplementor; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sqm.internal.QuerySqmImpl; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; + +/** + * @author Steve Ebersole + */ +public class SqlAstHelper { + public static SelectStatement translateHqlSelectQuery(String hql, Class returnType, SessionImplementor session) { + final QueryImplementor query = session.createQuery( hql, returnType ); + final QuerySqmImpl hqlQuery = (QuerySqmImpl) query; + final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); + + final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( + sqmStatement, + hqlQuery.getQueryOptions(), + hqlQuery.getDomainParameterXref(), + query.getParameterBindings(), + session.getLoadQueryInfluencers(), + session.getFactory(), + true + ); + + final SqmTranslation sqmInterpretation = sqmConverter.translate(); + return sqmInterpretation.getSqlAst(); + } +}