From 56947f28dc0325eadb2585316afbfe0160675660 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 29 May 2017 15:32:41 +0300 Subject: [PATCH] HHH-9576 - Use JDBC bind variables for handling JPA Criteria query numeric literals --- .../internal/SessionFactoryBuilderImpl.java | 20 ++- .../internal/SessionFactoryOptionsImpl.java | 8 ++ .../internal/SessionFactoryOptionsState.java | 5 + ...stractDelegatingSessionFactoryOptions.java | 6 + .../boot/spi/SessionFactoryOptions.java | 5 + .../org/hibernate/cfg/AvailableSettings.java | 20 +++ .../java/org/hibernate/dialect/Dialect.java | 18 +++ .../org/hibernate/dialect/MySQLDialect.java | 5 + .../query/criteria/LiteralHandlingMode.java | 29 ++++ .../internal/compile/CriteriaCompiler.java | 22 +++ .../internal/compile/RenderingContext.java | 27 +++- .../expression/LiteralExpression.java | 29 +++- ...stractCriteriaLiteralHandlingModeTest.java | 127 ++++++++++++++++++ .../CriteriaLiteralHandlingModeAutoTest.java | 26 ++++ .../CriteriaLiteralHandlingModeBindTest.java | 36 +++++ ...CriteriaLiteralHandlingModeInlineTest.java | 36 +++++ ...CriteriaLiteralHandlingModeInlineTest.java | 42 ++++++ .../nulliteral/CriteriaLiteralsTest.java | 45 +++++++ 18 files changed, 497 insertions(+), 9 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index a9ee66a01f..b595c7e160 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -44,7 +44,6 @@ import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.config.internal.ConfigurationServiceImpl; import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; @@ -53,6 +52,7 @@ import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; @@ -564,6 +564,7 @@ public static class SessionFactoryOptionsStateStandardImpl implements SessionFac private boolean wrapResultSetsEnabled; private TimeZone jdbcTimeZone; private boolean queryParametersValidationEnabled; + private LiteralHandlingMode criteriaLiteralHandlingMode; private Map sqlFunctions; @@ -776,6 +777,13 @@ else if ( jdbcTimeZoneValue != null ) { configurationSettings, true ); + + this.criteriaLiteralHandlingMode = strategySelector.resolveStrategy( + LiteralHandlingMode.class, + configurationSettings.get( CRITERIA_LITERAL_HANDLING_MODE ), + LiteralHandlingMode.AUTO, + (clazz) -> LiteralHandlingMode.AUTO + ); } private static Interceptor determineInterceptor(Map configurationSettings, StrategySelector strategySelector) { @@ -1216,6 +1224,11 @@ public TimeZone getJdbcTimeZone() { public boolean isQueryParametersValidationEnabled() { return this.queryParametersValidationEnabled; } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return this.criteriaLiteralHandlingMode; + } } @Override @@ -1544,4 +1557,9 @@ public TimeZone getJdbcTimeZone() { public boolean isQueryParametersValidationEnabled() { return options.isQueryParametersValidationEnabled(); } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return options.getCriteriaLiteralHandlingMode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java index 55e0505f6b..6cd6d4e3d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java @@ -28,6 +28,7 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -127,6 +128,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { private final Map sqlFunctions; private boolean queryParametersValidationEnabled; + private LiteralHandlingMode criteriaLiteralHandlingMode; public SessionFactoryOptionsImpl(SessionFactoryOptionsState state) { this.serviceRegistry = state.getServiceRegistry(); @@ -207,6 +209,7 @@ public SessionFactoryOptionsImpl(SessionFactoryOptionsState state) { this.jdbcTimeZone = state.getJdbcTimeZone(); this.queryParametersValidationEnabled = state.isQueryParametersValidationEnabled(); + this.criteriaLiteralHandlingMode = state.getCriteriaLiteralHandlingMode(); } @Override @@ -542,4 +545,9 @@ public TimeZone getJdbcTimeZone() { public boolean isQueryParametersValidationEnabled() { return queryParametersValidationEnabled; } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return criteriaLiteralHandlingMode; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java index a1158c811c..cb397230f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java @@ -28,6 +28,7 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -178,4 +179,8 @@ public interface SessionFactoryOptionsState { TimeZone getJdbcTimeZone(); boolean isQueryParametersValidationEnabled(); + + default LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return LiteralHandlingMode.AUTO; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index bd62dbfbfc..5daafb23a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -27,6 +27,7 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -383,4 +384,9 @@ public TimeZone getJdbcTimeZone() { public boolean isQueryParametersValidationEnabled() { return delegate.isQueryParametersValidationEnabled(); } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return delegate.getCriteriaLiteralHandlingMode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 052f700427..5ddcc351c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -27,6 +27,7 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -223,4 +224,8 @@ default boolean doesConnectionProviderDisableAutoCommit() { default boolean isQueryParametersValidationEnabled(){ return isJpaBootstrap(); } + + default LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return LiteralHandlingMode.AUTO; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 3ff3d7cd2c..67d224d6dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -1690,4 +1690,24 @@ public interface AvailableSettings { * */ String VALIDATE_QUERY_PARAMETERS = "hibernate.query.validate_parameters"; + + /** + * By default, Criteria queries uses bind parameters for any literal that is not a numeric value. + * + * However, to increase the likelihood of JDBC statement caching, + * you might want to use bind parameters for numeric values too. + * The {@link org.hibernate.query.criteria.LiteralHandlingMode#BIND} mode will use bind variables for any literal value. + * + * The {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} mode will inline literal values as-is. + * To prevent SQL injection, never use {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} with String variables. + * Always use constants with the {@link org.hibernate.query.criteria.LiteralHandlingMode#INLINE} mode. + *

+ * Valid options are defined by the {@link org.hibernate.query.criteria.LiteralHandlingMode} enum. + *

+ * The default value is {@link org.hibernate.query.criteria.LiteralHandlingMode#AUTO} + * + * @since 5.2.12 + * @see org.hibernate.query.criteria.LiteralHandlingMode + */ + String CRITERIA_LITERAL_HANDLING_MODE = "hibernate.criteria.literal_handling_mode"; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index c293374f6d..029bdbbc26 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -2921,6 +2921,24 @@ public boolean isLegacyLimitHandlerBehaviorEnabled() { return legacyLimitHandlerBehavior; } + /** + * Inline String literal. + * + * @return escaped String + */ + public String inlineLiteral(String literal) { + return String.format( "\'%s\'", escapeLiteral( literal ) ); + } + + /** + * Escape String literal. + * + * @return escaped String + */ + protected String escapeLiteral(String literal) { + return literal.replace("'", "''"); + } + private void resolveLegacyLimitHandlerBehavior(ServiceRegistry serviceRegistry) { // HHH-11194 // Temporary solution to set whether legacy limit handler behavior should be used. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index fdade45ab2..8d6084de19 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -583,4 +583,9 @@ public boolean dropConstraints() { protected MySQLStorageEngine getDefaultMySQLStorageEngine() { return MyISAMStorageEngine.INSTANCE; } + + @Override + protected String escapeLiteral(String literal) { + return super.escapeLiteral( literal ).replace("\\", "\\\\"); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.java new file mode 100644 index 0000000000..8b384ce017 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/LiteralHandlingMode.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 . + */ +package org.hibernate.query.criteria; + +/** + * This enum defines how literals are handled by JPA Criteria. + * + * By default ({@code AUTO}), Criteria queries uses bind parameters for any literal that is not a numeric value. + * + * However, to increase the likelihood of JDBC statement caching, + * you might want to use bind parameters for numeric values too. + * The {@code BIND} mode will use bind variables for any literal value. + * + * The {@code INLINE} mode will inline literal values as-is. + * To prevent SQL injection, never use {@code INLINE} with String variables. + * Always use constants with the {@code INLINE} mode. + * + * @author Vlad Mihalcea + */ +public enum LiteralHandlingMode { + + AUTO, + BIND, + INLINE +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java index 674658ae92..74ea10f606 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java @@ -14,9 +14,13 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.ParameterExpression; +import org.hibernate.SessionFactory; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.type.Type; @@ -47,6 +51,14 @@ public QueryImplementor compile(CompilableCriteria criteria) { final Map, ExplicitParameterInfo> explicitParameterInfoMap = new HashMap<>(); final List implicitParameterBindings = new ArrayList<>(); + final SessionFactoryImplementor sessionFactory = entityManager.getSessionFactory(); + + final LiteralHandlingMode criteriaLiteralHandlingMode = sessionFactory + .getSessionFactoryOptions() + .getCriteriaLiteralHandlingMode(); + + final Dialect dialect = sessionFactory.getServiceRegistry().getService( JdbcServices.class ).getDialect(); + RenderingContext renderingContext = new RenderingContext() { private int aliasCount; private int explicitParameterCount; @@ -122,6 +134,16 @@ public String getCastType(Class javaType) { } return hibernateType.getName(); } + + @Override + public Dialect getDialect() { + return dialect; + } + + @Override + public LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return criteriaLiteralHandlingMode; + } }; return criteria.interpret( renderingContext ).buildCompiledQuery( diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java index 586c8139d3..871ecb7b37 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java @@ -8,6 +8,9 @@ import javax.persistence.criteria.ParameterExpression; +import org.hibernate.dialect.Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + /** * Used to provide a context and services to the rendering. * @@ -19,7 +22,7 @@ public interface RenderingContext { * * @return The generated correlation name */ - public String generateAlias(); + String generateAlias(); /** * Register parameters explicitly encountered in the criteria query. @@ -28,7 +31,7 @@ public interface RenderingContext { * * @return The JPA-QL parameter name */ - public ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter); + ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter); /** * Register a parameter that was not part of the criteria query (at least not as a parameter). @@ -38,7 +41,7 @@ public interface RenderingContext { * * @return The JPA-QL parameter name */ - public String registerLiteralParameterBinding(Object literal, Class javaType); + String registerLiteralParameterBinding(Object literal, Class javaType); /** * Given a java type, determine the proper cast type name. @@ -47,5 +50,21 @@ public interface RenderingContext { * * @return The cast type name. */ - public String getCastType(Class javaType); + String getCastType(Class javaType); + + /** + * Current Dialect. + * + * @return Dialect + */ + Dialect getDialect(); + + /** + * How literals are going to be handled. + * + * @return literal handling strategy + */ + default LiteralHandlingMode getCriteriaLiteralHandlingMode() { + return LiteralHandlingMode.AUTO; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java index 2f9d0160af..0a611ec980 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java @@ -8,6 +8,7 @@ import java.io.Serializable; +import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.ValueHandlerFactory; @@ -46,11 +47,31 @@ public void registerParameters(ParameterRegistry registry) { @SuppressWarnings({ "unchecked" }) public String render(RenderingContext renderingContext) { - if ( ValueHandlerFactory.isNumeric( literal ) ) { - return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); - } - // else... + LiteralHandlingMode literalHandlingMode = renderingContext.getCriteriaLiteralHandlingMode(); + + switch ( literalHandlingMode ) { + case AUTO: + if ( ValueHandlerFactory.isNumeric( literal ) ) { + return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); + } + else { + return bindLiteral( renderingContext ); + } + case BIND: + return bindLiteral( renderingContext ); + case INLINE: + Object literalValue = literal; + if ( String.class.equals( literal.getClass() ) ) { + literalValue = renderingContext.getDialect().inlineLiteral( (String) literal ); + } + return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literalValue ); + default: + throw new IllegalArgumentException( "Unexpected LiteralHandlingMode: " + literalHandlingMode ); + } + } + + private String bindLiteral(RenderingContext renderingContext) { final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() ); return ':' + parameterName; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java new file mode 100644 index 0000000000..246bee0b25 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java @@ -0,0 +1,127 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +public abstract class AbstractCriteriaLiteralHandlingModeTest extends BaseEntityManagerFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider; + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + connectionProvider + ); + return config; + } + + @Override + public void buildEntityManagerFactory() throws Exception { + connectionProvider = new PreparedStatementSpyConnectionProvider(); + super.buildEntityManagerFactory(); + } + + @Override + public void releaseResources() { + super.releaseResources(); + connectionProvider.stop(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Book book = new Book(); + book.id = 1; + book.name = bookName(); + + entityManager.persist( book ); + } ); + } + + @Test + public void testLiteralHandlingMode() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + + final CriteriaQuery query = cb.createQuery( Tuple.class ); + + final Root entity = query.from( Book.class ); + query.where( + cb.and( + cb.equal( + entity.get( "id" ), + cb.literal( 1 ) + ), + cb.equal( + entity.get( "name" ), + cb.literal( bookName() ) + ) + ) + ); + + query.multiselect( + cb.literal( "abc" ), + entity.get( "name" ) + ); + + connectionProvider.clear(); + List tuples = entityManager.createQuery( query ) + .getResultList(); + assertEquals( 1, tuples.size() ); + + assertNotNull( connectionProvider.getPreparedStatement( expectedSQL() ) ); + } ); + } + + protected abstract String expectedSQL(); + + @Entity(name = "Book") + public static class Book { + + @Id + private Integer id; + + private String name; + } + + protected String bookName() { + return "Vlad's High-Performance Java Persistence"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java new file mode 100644 index 0000000000..f9b8133277 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeAutoTest.java @@ -0,0 +1,26 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeAutoTest extends AbstractCriteriaLiteralHandlingModeTest { + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name=?"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java new file mode 100644 index 0000000000..9bc5d34a95 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeBindTest.java @@ -0,0 +1,36 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeBindTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.BIND + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=? and abstractcr0_.name=?"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java new file mode 100644 index 0000000000..8d566cecda --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralHandlingModeInlineTest.java @@ -0,0 +1,36 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class CriteriaLiteralHandlingModeInlineTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.INLINE + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad''s High-Performance Java Persistence'"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java new file mode 100644 index 0000000000..9e008ccf78 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/MySQLCriteriaLiteralHandlingModeInlineTest.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.query.criteria.LiteralHandlingMode; + +import org.hibernate.testing.RequiresDialect; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(MySQLDialect.class) +public class MySQLCriteriaLiteralHandlingModeInlineTest extends AbstractCriteriaLiteralHandlingModeTest { + + @Override + protected Map getConfig() { + Map config = super.getConfig(); + config.put( + AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, + LiteralHandlingMode.INLINE + ); + return config; + } + + protected String expectedSQL() { + return "select 'abc' as col_0_0_, abstractcr0_.name as col_1_0_ from Book abstractcr0_ where abstractcr0_.id=1 and abstractcr0_.name='Vlad\\\\''s High-Performance Java Persistence'"; + } + + @Override + protected String bookName() { + return "Vlad\\'s High-Performance Java Persistence"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java index d562e58a59..ea84fdb2c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java @@ -11,6 +11,7 @@ import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.EntityManager; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; @@ -141,6 +142,50 @@ public void testLiteralsInWhereClause() throws Exception { } ); } + @Test + public void testNumericLiteralsInWhereClause() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + testNumericLiterals( + entityManager, + "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.id=1" + ); + } ); + } + + @Test + public void testNumericLiteralsInWhereClauseUsingBindParameters() throws Exception { + doInJPA( this::entityManagerFactory, entityManager -> { + testNumericLiterals( + entityManager, + "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.id=1" + ); + } ); + } + + private void testNumericLiterals(EntityManager entityManager, String expectedSQL) { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + + final CriteriaQuery query = cb.createQuery( Tuple.class ); + + final Root entity = query.from( Book.class ); + query.where( cb.equal( + entity.get( "id" ), + cb.literal( 1 ) + ) ); + + query.multiselect( + cb.literal( "abc" ), + entity.get( "name" ) + ); + + connectionProvider.clear(); + List tuples = entityManager.createQuery( query ) + .getResultList(); + assertEquals( 1, tuples.size() ); + + assertNotNull( connectionProvider.getPreparedStatement(expectedSQL) ); + } + @Test public void testCriteriaParameters() throws Exception { doInJPA( this::entityManagerFactory, entityManager -> {