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:
parent
0c8779e1ee
commit
4d9fb70114
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue