HHH-12290 Expand ordinal parameters to ordinal parameters

They used to be expanded to named parameters which causes problem with
strict JPA compliance as named and positional parameters cannot be
mixed.

The first value is replaced by the very same initial parameter position to avoid
gaps (these are not supported), then we replace the other values with max position
+ increment.
This commit is contained in:
Guillaume Smet 2018-03-09 17:51:12 +01:00 committed by Steve Ebersole
parent 0c8779e1ee
commit 4d9fb70114
3 changed files with 118 additions and 33 deletions

View File

@ -216,20 +216,19 @@ public final class StringHelper {
* Used to find the ordinal parameters (e.g. '?1') in a string.
*/
public static int indexOfIdentifierWord(String str, String word) {
if ( str == null || str.length() == 0 || word == null ) {
if ( str == null || str.length() == 0 || word == null || word.length() == 0 ) {
return -1;
}
int position = 0;
while ( position < str.length() ) {
position = str.indexOf( word, position );
int position = str.indexOf( word );
while ( position >= 0 && position < str.length() ) {
if (
( position == 0 || !Character.isJavaIdentifierPart( str.charAt( position - 1 ) ) ) &&
( position + word.length() == str.length() || !Character.isJavaIdentifierPart( str.charAt( position + word.length() ) ) )
) {
return position;
}
position = position + 1;
position = str.indexOf( word, position + 1 );
}
return -1;

View File

@ -14,6 +14,8 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.Parameter;
import org.hibernate.HibernateException;
@ -56,6 +58,8 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
private final int ordinalParamValueOffset;
private final int jdbcStyleOrdinalCountBase;
private Map<QueryParameter, QueryParameterBinding> parameterBindingMap;
private Map<QueryParameter, QueryParameterListBinding> parameterListBindingMap;
private Set<QueryParameter> parametersConvertedToListBindings;
@ -85,6 +89,8 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
this.parameterBindingMap = CollectionHelper.concurrentMap( parameterMetadata.getParameterCount() );
this.jdbcStyleOrdinalCountBase = sessionFactory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() ? 0 : 1;
if ( parameterMetadata.hasPositionalParameters() ) {
int smallestOrdinalParamLabel = Integer.MAX_VALUE;
for ( QueryParameter queryParameter : parameterMetadata.getPositionalParameters() ) {
@ -516,6 +522,8 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
final Dialect dialect = session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getJdbcEnvironment().getDialect();
final int inExprLimit = dialect.getInExpressionCountLimit();
int maxOrdinalPosition = getMaxOrdinalPosition();
for ( Map.Entry<QueryParameter, QueryParameterListBinding> entry : parameterListBindingMap.entrySet() ) {
final QueryParameter sourceParam = entry.getKey();
final Collection bindValues = entry.getValue().getBindValues();
@ -564,29 +572,48 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
expansionList.append( ", " );
}
// for each value in the bound list-of-values we:
// 1) create a synthetic named parameter
// 2) expand the queryString to include each synthetic named param in place of the original
// 3) create a new synthetic binding for just that single value under the synthetic name
final String syntheticName;
final QueryParameter syntheticParam;
if ( sourceParam instanceof NamedParameterDescriptor ) {
syntheticName = NamedParameterDescriptor.class.cast( sourceParam ).getName() + '_' + i;
// in the case of a named parameter, for each value in the bound list-of-values we:
// 1) create a synthetic named parameter
// 2) expand the queryString to include each synthetic named param in place of the original
// 3) create a new synthetic binding for just that single value under the synthetic name
final String syntheticName = NamedParameterDescriptor.class.cast( sourceParam ).getName() + '_' + i;
expansionList.append( ":" ).append( syntheticName );
syntheticParam = new NamedParameterDescriptor(
syntheticName,
sourceParam.getType(),
sourceParam.getSourceLocations()
);
}
else {
syntheticName = "x" + OrdinalParameterDescriptor.class.cast( sourceParam ).getPosition() + '_' + i;
// in the case of an ordinal parameter, for each value in the bound list-of-values we:
// 1) create a new ordinal parameter at a synthetic position of maxOrdinalPosition + 1
// 2) expand the queryString to include each new ordinal param in place of the original
// 3) create a new ordinal binding for just that single value under the synthetic position
// for the first item, we reuse the original parameter to avoid gaps in the positions
if ( i == 0 ) {
syntheticParam = sourceParam;
}
else {
int syntheticPosition = ++maxOrdinalPosition;
syntheticParam = new OrdinalParameterDescriptor(
syntheticPosition,
syntheticPosition - jdbcStyleOrdinalCountBase,
sourceParam.getType(),
sourceParam.getSourceLocations()
);
}
expansionList.append( "?" ).append( syntheticParam.getPosition() );
}
expansionList.append( ":" ).append( syntheticName );
final QueryParameter syntheticParam = new NamedParameterDescriptor(
syntheticName,
sourceParam.getType(),
sourceParam.getSourceLocations()
);
final QueryParameterBinding syntheticBinding = makeBinding( entry.getValue().getBindType() );
syntheticBinding.setBindValue( bindValue );
parameterBindingMap.put( syntheticParam, syntheticBinding );
i++;
}
@ -606,4 +633,19 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
return queryString;
}
private int getMaxOrdinalPosition() {
int maxOrdinalPosition = 0;
for ( QueryParameter<?> queryParameter : parameterBindingMap.keySet() ) {
if ( queryParameter instanceof OrdinalParameterDescriptor ) {
maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() );
}
}
for ( QueryParameter<?> queryParameter : parameterListBindingMap.keySet() ) {
if ( queryParameter instanceof OrdinalParameterDescriptor ) {
maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() );
}
}
return maxOrdinalPosition;
}
}

View File

@ -6,20 +6,21 @@
*/
package org.hibernate.test.jpa.ql;
import org.hibernate.hql.internal.ast.QuerySyntaxException;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.test.jpa.AbstractJPATest;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import org.hibernate.Session;
import org.hibernate.hql.internal.ast.QuerySyntaxException;
import org.hibernate.query.Query;
import org.hibernate.test.jpa.AbstractJPATest;
import org.hibernate.test.jpa.Item;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
/**
* Tests for various JPAQL compliance issues
@ -112,9 +113,9 @@ public class JPAQLComplianceTest extends AbstractJPATest {
Session s = openSession();
try {
Query q = s.createQuery( "select item from Item item where item.id in (?1) and item.name = :name" );
List<Integer> params = new ArrayList();
params.add( 0 );
params.add( 1 );
List<Long> params = new ArrayList<>();
params.add( 0L );
params.add( 1L );
q.setParameter( 1, params );
q.setParameter( "name", "name" );
q.list();
@ -128,4 +129,47 @@ public class JPAQLComplianceTest extends AbstractJPATest {
s.close();
}
}
@Test
@TestForIssue(jiraKey = "HHH-12290")
public void testParameterCollectionParenthesesAndPositional() {
final Item item = new Item( "Mouse" );
item.setId( 1L );
final Item item2 = new Item( "Computer" );
item2.setId( 2L );
Session s = openSession();
try {
s.getTransaction().begin();
s.save( item );
s.save( item2 );
s.getTransaction().commit();
s.getTransaction().begin();
Query q = s.createQuery( "select item from Item item where item.id in(?1) and item.name in (?2) and item.id in(?1)" );
List<Long> idParams = new ArrayList<>();
idParams.add( item.getId() );
idParams.add( item2.getId() );
q.setParameter( 1, idParams );
List<String> nameParams = new ArrayList<>();
nameParams.add( item.getName() );
nameParams.add( item2.getName() );
q.setParameter( 2, nameParams );
List result = q.getResultList();
assertNotNull( result );
assertEquals( 2, result.size() );
}
catch (Exception e){
if ( s.getTransaction() != null && s.getTransaction().isActive() ) {
s.getTransaction().rollback();
}
throw e;
}
finally {
s.close();
}
}
}