HHH-14876 Parameter padding for IN clauses doesn't work in Hibernate 6.0.0.Beta1

This commit is contained in:
Andrea Boriero 2021-10-18 22:06:26 +02:00 committed by Andrea Boriero
parent 3f6a782760
commit f9d0b7d069
12 changed files with 642 additions and 473 deletions

View File

@ -12,6 +12,7 @@ import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.FetchClauseType;
import org.hibernate.query.IntervalType;
@ -3068,7 +3069,18 @@ public abstract class Dialect implements ConversionContext {
protected final BatchLoadSizingStrategy STANDARD_DEFAULT_BATCH_LOAD_SIZING_STRATEGY = new BatchLoadSizingStrategy() {
@Override
public int determineOptimalBatchLoadSize(int numberOfKeyColumns, int numberOfKeys) {
return 50;
int paddedSize = MathHelper.ceilingPowerOfTwo( numberOfKeys );
// For tuples, there is no limit, so we can just use the power of two padding approach
if ( numberOfKeyColumns > 1 ) {
return paddedSize;
}
if ( paddedSize < getInExpressionCountLimit() ) {
return paddedSize;
}
else if ( numberOfKeys < getInExpressionCountLimit() ) {
return numberOfKeys;
}
return getInExpressionCountLimit();
}
};

View File

@ -13,6 +13,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
@ -4866,17 +4867,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
@Override
public InListPredicate visitInListPredicate(SqmInListPredicate<?> predicate) {
public Predicate visitInListPredicate(SqmInListPredicate<?> predicate) {
// special case: if there is just a single "value" element and it is a parameter
// and the binding for that parameter is multi-valued we need special
// handling for "expansion"
if ( predicate.getListExpressions().size() == 1 ) {
final SqmExpression sqmExpression = predicate.getListExpressions().get( 0 );
final SqmExpression<?> sqmExpression = predicate.getListExpressions().get( 0 );
if ( sqmExpression instanceof SqmParameter ) {
final SqmParameter sqmParameter = (SqmParameter) sqmExpression;
final SqmParameter<?> sqmParameter = (SqmParameter<?>) sqmExpression;
if ( sqmParameter.allowMultiValuedBinding() ) {
final InListPredicate specialCase = processInListWithSingleParameter( predicate, sqmParameter );
final Predicate specialCase = processInListWithSingleParameter( predicate, sqmParameter );
if ( specialCase != null ) {
return specialCase;
}
@ -4895,7 +4896,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression() ) );
try {
for ( SqmExpression expression : predicate.getListExpressions() ) {
for ( SqmExpression<?> expression : predicate.getListExpressions() ) {
inPredicate.addExpression( (Expression) expression.accept( this ) );
}
}
@ -4906,70 +4907,49 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return inPredicate;
}
private InListPredicate processInListWithSingleParameter(
private Predicate processInListWithSingleParameter(
SqmInListPredicate<?> sqmPredicate,
SqmParameter sqmParameter) {
SqmParameter<?> sqmParameter) {
assert sqmParameter.allowMultiValuedBinding();
if ( sqmParameter instanceof JpaCriteriaParameter ) {
return processInSingleCriteriaParameter( sqmPredicate, (JpaCriteriaParameter) sqmParameter );
return processInSingleCriteriaParameter( sqmPredicate, (JpaCriteriaParameter<?>) sqmParameter );
}
return processInSingleHqlParameter( sqmPredicate, sqmParameter );
}
private InListPredicate processInSingleHqlParameter(SqmInListPredicate<?> sqmPredicate, SqmParameter sqmParameter) {
private Predicate processInSingleHqlParameter(SqmInListPredicate<?> sqmPredicate, SqmParameter<?> sqmParameter) {
final QueryParameterImplementor<?> domainParam = domainParameterXref.getQueryParameter( sqmParameter );
final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( domainParam );
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( domainParam );
if ( !domainParamBinding.isMultiValued() ) {
// triggers normal processing
return null;
}
final InListPredicate inListPredicate = new InListPredicate(
(Expression) sqmPredicate.getTestExpression().accept( this ),
sqmPredicate.isNegated(),
getBooleanType()
);
inferrableTypeAccessStack.push(
() -> determineValueMapping( sqmPredicate.getTestExpression() )
);
try {
boolean first = true;
for ( Object bindValue : domainParamBinding.getBindValues() ) {
final SqmParameter sqmParamToConsume;
// for each bind value create an "expansion"
if ( first ) {
sqmParamToConsume = sqmParameter;
first = false;
}
else {
sqmParamToConsume = sqmParameter.copy();
domainParameterXref.addExpansion( domainParam, sqmParameter, sqmParamToConsume );
}
inListPredicate.addExpression( consumeSingleSqmParameter( sqmParamToConsume ) );
}
}
finally {
inferrableTypeAccessStack.pop();
}
return inListPredicate;
return processInSingleParameter( sqmPredicate, sqmParameter, domainParam, domainParamBinding );
}
private InListPredicate processInSingleCriteriaParameter(
private Predicate processInSingleCriteriaParameter(
SqmInListPredicate<?> sqmPredicate,
JpaCriteriaParameter jpaCriteriaParameter) {
JpaCriteriaParameter<?> jpaCriteriaParameter) {
assert jpaCriteriaParameter.allowsMultiValuedBinding();
final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter );
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter );
if ( !domainParamBinding.isMultiValued() ) {
return null;
}
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter )
.get();
return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding );
}
private Predicate processInSingleParameter(
SqmInListPredicate<?> sqmPredicate,
SqmParameter<?> sqmParameter,
QueryParameterImplementor<?> domainParam,
QueryParameterBinding<?> domainParamBinding) {
final InListPredicate inListPredicate = new InListPredicate(
(Expression) sqmPredicate.getTestExpression().accept( this ),
@ -4981,31 +4961,22 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
() -> determineValueMapping( sqmPredicate.getTestExpression() )
);
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter )
.get();
try {
boolean first = true;
for ( Object bindValue : domainParamBinding.getBindValues() ) {
final SqmParameter sqmParamToConsume;
final Iterator<?> iterator = domainParamBinding.getBindValues().iterator();
inListPredicate.addExpression( consumeSingleSqmParameter( sqmParameter ) );
iterator.next();
while ( iterator.hasNext() ) {
iterator.next();
// for each bind value create an "expansion"
if ( first ) {
sqmParamToConsume = sqmWrapper;
first = false;
}
else {
sqmParamToConsume = sqmWrapper.copy();
domainParameterXref.addExpansion( jpaCriteriaParameter, sqmWrapper, sqmParamToConsume );
}
final SqmParameter<?> sqmParamToConsume = sqmParameter.copy();
domainParameterXref.addExpansion( domainParam, sqmParameter, sqmParamToConsume );
inListPredicate.addExpression( consumeSingleSqmParameter( sqmParamToConsume ) );
}
return inListPredicate;
}
finally {
inferrableTypeAccessStack.pop();
}
return inListPredicate;
}
@Override

View File

@ -15,6 +15,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -26,12 +27,14 @@ import org.hibernate.LockMode;
import org.hibernate.QueryException;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.SelectItemReferenceStrategy;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.query.FetchClauseType;
@ -4081,22 +4084,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
appendSql( "1=0" );
return;
}
Function<Expression, Expression> itemAccessor = Function.identity();
final SqlTuple lhsTuple;
if ( ( lhsTuple = SqlTupleContainer.getSqlTuple( inListPredicate.getTestExpression() ) ) != null ) {
if ( lhsTuple.getExpressions().size() == 1 ) {
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
lhsTuple.getExpressions().get( 0 ).accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
String separator = NO_SEPARATOR;
for ( Expression expression : listExpressions ) {
appendSql( separator );
SqlTupleContainer.getSqlTuple( expression ).getExpressions().get( 0 ).accept( this );
separator = COMA_SEPARATOR;
}
appendSql( CLOSE_PARENTHESIS );
itemAccessor = listExpression -> SqlTupleContainer.getSqlTuple( listExpression ).getExpressions().get( 0 );
}
else if ( !supportsRowValueConstructorSyntaxInInList() ) {
final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ?
@ -4132,26 +4125,108 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
separator = " or ";
}
}
return;
}
else {
}
inListPredicate.getTestExpression().accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
String separator = NO_SEPARATOR;
int bindValueCount = listExpressions.size();
int bindValueMaxCount = bindValueCount;
final Dialect dialect = getSessionFactory().getJdbcServices().getDialect();
int inExprLimit = dialect.getInExpressionCountLimit();
final boolean inClauseParameterPaddingEnabled = getSessionFactory().
getSessionFactoryOptions().inClauseParameterPaddingEnabled()
&& bindValueCount > 2;
if ( inClauseParameterPaddingEnabled ) {
// bindValueCount: 1005
// bindValuePaddingCount: 1024
int bindValuePaddingCount = MathHelper.ceilingPowerOfTwo( bindValueCount );
// inExprLimit: 1000
if ( inExprLimit > 0 ) {
if ( bindValuePaddingCount > inExprLimit ) {
// bindValueCount % inExprLimit: 5
// bindValuePaddingCount: 8
if ( bindValueCount < inExprLimit ) {
bindValueMaxCount = inExprLimit;
}
else {
bindValueMaxCount = MathHelper.ceilingPowerOfTwo( bindValueCount % inExprLimit );
}
}
else if ( bindValueCount < bindValuePaddingCount ) {
bindValueMaxCount = bindValuePaddingCount;
}
}
else if ( bindValueCount < bindValuePaddingCount ) {
bindValueMaxCount = bindValuePaddingCount;
}
}
final Iterator<Expression> iterator = listExpressions.iterator();
int itemNumber = 0;
while ( iterator.hasNext() && ( inExprLimit == 0 || itemNumber < inExprLimit ) ) {
final Expression listExpression = itemAccessor.apply( iterator.next() );
appendSql( separator );
listExpression.accept( this );
separator = COMA_SEPARATOR;
itemNumber++;
// If we encounter an expression that is not a parameter, we reset the inExprLimit and bindValueMaxCount
// and just render through the in list expressions as they are without padding/splitting
if ( !( listExpression instanceof JdbcParameter || listExpression instanceof SqmParameterInterpretation ) ) {
inExprLimit = 0;
bindValueMaxCount = bindValueCount;
}
}
if ( itemNumber != inExprLimit && bindValueCount == bindValueMaxCount ) {
appendSql( CLOSE_PARENTHESIS );
return;
}
if ( inExprLimit > 0 && bindValueCount > inExprLimit ) {
do {
append( ") and " );
inListPredicate.getTestExpression().accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
renderCommaSeparated( listExpressions );
appendSql( CLOSE_PARENTHESIS );
}
separator = NO_SEPARATOR;
itemNumber = 0;
while ( iterator.hasNext() && itemNumber < inExprLimit ) {
final Expression listExpression = iterator.next();
appendSql( separator );
itemAccessor.apply( listExpression ).accept( this );
separator = COMA_SEPARATOR;
itemNumber++;
}
} while ( iterator.hasNext() );
}
int i;
if ( inExprLimit > 0 && bindValueCount > inExprLimit ) {
i = bindValueCount % inExprLimit;
}
else {
inListPredicate.getTestExpression().accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
renderCommaSeparated( listExpressions );
appendSql( CLOSE_PARENTHESIS );
i = bindValueCount;
}
final Expression lastExpression = itemAccessor.apply( listExpressions.get( listExpressions.size() - 1 ) );
for ( ; i < bindValueMaxCount; i++ ) {
appendSql( separator );
lastExpression.accept( this );
separator = COMA_SEPARATOR;
}
appendSql( CLOSE_PARENTHESIS );
}
@Override

View File

@ -0,0 +1,135 @@
/*
* 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.query;
import java.util.Arrays;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.ParameterExpression;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-13108")
@Jpa(
annotatedClasses = InClauseParameterPaddingCriteriaTest.Document.class,
integrationSettings = {
@Setting(name = AvailableSettings.USE_SQL_COMMENTS, value = "true"),
@Setting(name = AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, value = "true"),
},
statementInspectorClass = SQLStatementInspector.class
)
public class InClauseParameterPaddingCriteriaTest {
@BeforeAll
protected void afterEntityManagerFactoryBuilt(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
Document document = new Document();
document.setName( "A" );
entityManager.persist( document );
} );
}
@Test
public void testInClauseParameterPadding(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction( entityManager -> {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> query = cb.createQuery( Integer.class );
Root<Document> document = query.from( Document.class );
ParameterExpression<List> inClauseParams = cb.parameter( List.class, "ids" );
query.select( document.get( "id" ) )
.where( document.get( "id" ).in( inClauseParams ) );
List<Integer> ids = entityManager.createQuery( query )
.setParameter( "ids", Arrays.asList( 1, 2, 3, 4, 5 ) )
.getResultList();
assertEquals( 1, ids.size() );
} );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( "in(?,?,?,?,?,?,?,?)" ) );
}
@Test
public void testInClauseParameterPaddingForExpressions(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction( entityManager -> {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> query = cb.createQuery( Integer.class );
Root<Document> document = query.from( Document.class );
query.select( document.get( "id" ) )
.where(
document.get( "id" ).in(
document.get( "id" ),
document.get( "id" ),
document.get( "id" )
)
);
List<Integer> ids = entityManager.createQuery( query )
.getResultList();
assertEquals( 1, ids.size() );
} );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( "in(d1_0.id,d1_0.id,d1_0.id)" ) );
}
@Entity(name = "Document")
public static class Document {
@Id
@GeneratedValue
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.query;
import java.util.Arrays;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-12469")
@Jpa(
annotatedClasses = { InClauseParameterPaddingTest.Person.class },
integrationSettings = {
@Setting(name = AvailableSettings.USE_SQL_COMMENTS, value = "true"),
@Setting(name = AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, value = "true")
},
statementInspectorClass = SQLStatementInspector.class
)
public class InClauseParameterPaddingTest {
@BeforeEach
protected void afterEntityManagerFactoryBuilt(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
for ( int i = 1; i < 10; i++ ) {
Person person = new Person();
person.setId( i );
person.setName( String.format( "Person nr %d", i ) );
entityManager.persist( person );
}
} );
}
@Test
public void testInClauseParameterPadding(EntityManagerFactoryScope scope) {
validateInClauseParameterPadding( scope, "in(?)", 1 );
validateInClauseParameterPadding( scope, "in(?,?)", 1, 2 );
validateInClauseParameterPadding( scope, "in(?,?,?,?)", 1, 2, 3 );
validateInClauseParameterPadding( scope, "in(?,?,?,?)", 1, 2, 3, 4 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5, 6 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5, 6, 7 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5, 6, 7, 8 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5, 6, 7, 8, 9 );
validateInClauseParameterPadding( scope, "in(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );
}
private void validateInClauseParameterPadding(
EntityManagerFactoryScope scope,
String expectedInClause,
Integer... ids) {
final SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( entityManager -> {
entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id in :ids" )
.setParameter( "ids", Arrays.asList( ids ) )
.getResultList();
} );
assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause ) );
}
@Entity(name = "Person")
public static class Person {
@Id
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,240 @@
/*
* 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.query;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.jpa.NonStringValueSettingProvider;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-12469")
@RequiresDialect(H2Dialect.class)
@Jpa(
annotatedClasses = { MaxInExpressionParameterPaddingTest.Person.class },
integrationSettings = {
@Setting(name = AvailableSettings.USE_SQL_COMMENTS, value = "true"),
@Setting(name = AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, value = "true"),
},
nonStringValueSettingProviders = MaxInExpressionParameterPaddingTest.DialectProvider.class,
statementInspectorClass = SQLStatementInspector.class
)
public class MaxInExpressionParameterPaddingTest {
public static class DialectProvider extends NonStringValueSettingProvider {
@Override
public String getKey() {
return AvailableSettings.DIALECT;
}
@Override
public Object getValue() {
return MaxInExpressionParameterPaddingTest.MaxCountInExpressionH2Dialect.class.getName();
}
}
public static final int MAX_COUNT = 15;
@BeforeAll
protected void afterEntityManagerFactoryBuilt(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
for ( int i = 0; i < MAX_COUNT; i++ ) {
Person person = new Person();
person.setId( i );
person.setName( String.format( "Person nr %d", i ) );
entityManager.persist( person );
}
} );
}
@Test
public void testInClauseParameterPadding(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction( entityManager -> {
entityManager.createQuery( "select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, MAX_COUNT ).boxed().collect( Collectors.toList() ) )
.getResultList();
} );
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in(?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@TestForIssue(jiraKey = "HHH-14109")
@Test
public void testInClauseParameterPaddingToLimit(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
entityManager ->
entityManager.createQuery(
"select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, 10 )
.boxed()
.collect( Collectors.toList() ) )
.getResultList()
);
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in(?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@Test
public void testInClauseParameterSplittingAfterLimit(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
entityManager ->
entityManager.createQuery(
"select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, 16 )
.boxed()
.collect( Collectors.toList() ) )
.getResultList()
);
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in(?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
expectedInClause.append( " and p1_0.id in(?)" );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@Test
public void testInClauseParameterSplittingAfterLimit2(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
entityManager ->
entityManager.createQuery(
"select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, 18 )
.boxed()
.collect( Collectors.toList() ) )
.getResultList()
);
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in(?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
expectedInClause.append( " and p1_0.id in(?,?,?,?)" );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@Test
public void testInClauseParameterSplittingAfterLimit3(EntityManagerFactoryScope scope) {
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
entityManager ->
entityManager.createQuery(
"select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, 33 )
.boxed()
.collect( Collectors.toList() ) )
.getResultList()
);
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in(?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
expectedInClause.append( " and p1_0.id in(?");
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( ",?" );
}
expectedInClause.append( ")" );
expectedInClause.append( " and p1_0.id in(?,?,?,?)" );
assertTrue( statementInspector.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@Entity(name = "Person")
public static class Person {
@Id
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class MaxCountInExpressionH2Dialect extends H2Dialect {
public MaxCountInExpressionH2Dialect() {
}
@Override
public int getInExpressionCountLimit() {
return MAX_COUNT;
}
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.query;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.ParameterExpression;
import jakarta.persistence.criteria.Root;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-13108")
public class InClauseParameterPaddingCriteriaTest extends BaseEntityManagerFunctionalTestCase {
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
protected void addConfigOptions(Map options) {
sqlStatementInterceptor = new SQLStatementInterceptor( options );
options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() );
options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() );
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Document.class
};
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
Document document = new Document();
document.setName( "A" );
entityManager.persist( document );
} );
}
@Test
public void testInClauseParameterPadding() {
sqlStatementInterceptor.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery( Long.class );
Root<Document> document = query.from( Document.class );
ParameterExpression<List> inClauseParams = cb.parameter( List.class, "ids" );
query
.select( document.get( "id" ) )
.where(
document.get( "id" ).in( inClauseParams )
);
List<Long> ids = entityManager.createQuery( query )
.setParameter(
"ids",
Arrays.asList(
1,
2,
3,
4,
5
)
)
.getResultList();
assertEquals( 1, ids.size() );
} );
assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( "in (? , ? , ? , ? , ? , ? , ? , ?)" ) );
}
@Entity(name = "Document")
public static class Document {
@Id
@GeneratedValue
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -1,113 +0,0 @@
/*
* 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.query;
import java.util.Arrays;
import java.util.Map;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-12469" )
public class InClauseParameterPaddingTest extends BaseEntityManagerFunctionalTestCase {
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
protected void addConfigOptions(Map options) {
sqlStatementInterceptor = new SQLStatementInterceptor( options );
options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() );
options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() );
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Person.class
};
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( int i = 1; i < 10; i++ ) {
Person person = new Person();
person.setId( i );
person.setName( String.format( "Person nr %d", i ) );
entityManager.persist( person );
}
} );
}
@Test
public void testInClauseParameterPadding() {
validateInClauseParameterPadding( "in (?)", 1 );
validateInClauseParameterPadding( "in (? , ?)", 1, 2 );
validateInClauseParameterPadding( "in (? , ? , ? , ?)", 1, 2, 3 );
validateInClauseParameterPadding( "in (? , ? , ? , ?)", 1, 2, 3, 4 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8, 9 );
validateInClauseParameterPadding( "in (? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ?)", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );
}
private void validateInClauseParameterPadding(String expectedInClause, Integer... ids) {
sqlStatementInterceptor.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id in :ids" )
.setParameter( "ids", Arrays.asList(ids) )
.getResultList();
} );
assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause ));
}
@Entity(name = "Person")
public static class Person {
@Id
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -1,147 +0,0 @@
/*
* 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.query;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-12469" )
@RequiresDialect(H2Dialect.class)
public class MaxInExpressionParameterPaddingTest extends BaseEntityManagerFunctionalTestCase {
public static final int MAX_COUNT = 15;
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
protected void addConfigOptions(Map options) {
sqlStatementInterceptor = new SQLStatementInterceptor( options );
options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() );
options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() );
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Person.class
};
}
@Override
protected Dialect getDialect() {
return new MaxCountInExpressionH2Dialect();
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( int i = 0; i < MAX_COUNT; i++ ) {
Person person = new Person();
person.setId( i );
person.setName( String.format( "Person nr %d", i ) );
entityManager.persist( person );
}
} );
}
@Test
public void testInClauseParameterPadding() {
sqlStatementInterceptor.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery( "select p from Person p where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, MAX_COUNT ).boxed().collect( Collectors.toList() ) )
.getResultList();
} );
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in (?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( " , ?" );
}
expectedInClause.append( ")" );
assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ) );
}
@TestForIssue( jiraKey = "HHH-14109" )
@Test
public void testInClauseParameterPaddingToLimit() {
sqlStatementInterceptor.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id in :ids" )
.setParameter( "ids", IntStream.range( 0, 10 ).boxed().collect(Collectors.toList()) )
.getResultList();
} );
StringBuilder expectedInClause = new StringBuilder();
expectedInClause.append( "in (?" );
for ( int i = 1; i < MAX_COUNT; i++ ) {
expectedInClause.append( " , ?" );
}
expectedInClause.append( ")" );
assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() ));
}
@Entity(name = "Person")
public static class Person {
@Id
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class MaxCountInExpressionH2Dialect extends H2Dialect {
@Override
public int getInExpressionCountLimit() {
return MAX_COUNT;
}
}
}

View File

@ -26,6 +26,7 @@ import org.hibernate.jpa.boot.spi.Bootstrap;
import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder;
import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStrategy;
import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.tool.schema.Action;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping;
@ -99,6 +100,9 @@ public class EntityManagerFactoryExtension
pui.getProperties().put( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, emfAnn.generatorScopeComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, emfAnn.orderByMappingComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_LOAD_BY_ID_COMPLIANCE, emfAnn.loadByIdComplianceEnabled() );
if ( !emfAnn.statementInspectorClass().equals( StatementInspector.class ) ) {
pui.getProperties().put( AvailableSettings.STATEMENT_INSPECTOR, emfAnn.statementInspectorClass() );
}
final Setting[] properties = emfAnn.properties();
for ( int i = 0; i < properties.length; i++ ) {

View File

@ -28,4 +28,6 @@ public interface EntityManagerFactoryScope {
<T> T fromEntityManager(Function<EntityManager, T> action);
<T> T fromTransaction(Function<EntityManager, T> action);
<T> T fromTransaction(EntityManager entityManager, Function<EntityManager, T> action);
}

View File

@ -17,6 +17,7 @@ import jakarta.persistence.ValidationMode;
import jakarta.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.testing.orm.domain.DomainModelDescriptor;
import org.hibernate.testing.orm.domain.StandardDomainModel;
@ -108,6 +109,8 @@ public @interface Jpa {
*/
boolean loadByIdComplianceEnabled() default false;
Class<? extends StatementInspector> statementInspectorClass() default StatementInspector.class;
boolean excludeUnlistedClasses() default false;
StandardDomainModel[] standardModels() default {};