DATAJPA-461 - Fixed regression in parameter binding of arrays.

Enhanced binding of parameters in StringQueryParameterBinder to be able to deal with situations where a parameter value has to be converted to be correctly bound e.g. for parameter values in IN-expressions.

We now only convert array values to collections if the value is to be bound in the context of an IN-parameter. Previously we erroneously always converted an array value to a collection value which lead to problems if an array value was meant to be used "as-is" e.g. in cases where an user wants to query for a certain byte[].

Original pull request: #56.
This commit is contained in:
Thomas Darimont
2014-02-10 15:53:18 +01:00
committed by Oliver Gierke
parent c85caf00d4
commit f8b0917c90
8 changed files with 502 additions and 179 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2013 the original author or authors.
* Copyright 2008-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,6 @@
*/
package org.springframework.data.jpa.repository.query;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import javax.persistence.Query;
@@ -27,7 +24,6 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link ParameterBinder} is used to bind method parameters to a {@link Query}. This is usually done whenever an
@@ -132,7 +128,7 @@ public class ParameterBinder {
return;
}
Object valueToUse = convertArrayToCollectionIfNecessary(value);
Object valueToUse = value;
if (hasNamedParameter(query) && parameter.isNamedParameter()) {
query.setParameter(parameter.getName(), valueToUse);
@@ -141,27 +137,6 @@ public class ParameterBinder {
}
}
/**
* Returns the given value as collection if it is an array or as is if not.
*
* @return
*/
private Object convertArrayToCollectionIfNecessary(Object value) {
if (!ObjectUtils.isArray(value)) {
return value;
}
int length = Array.getLength(value);
Collection<Object> result = new ArrayList<Object>(length);
for (int i = 0; i < length; i++) {
result.add(Array.get(value, i));
}
return result;
}
/**
* Binds the parameters to the given query and applies special parameter types (e.g. pagination).
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,17 @@ package org.springframework.data.jpa.repository.query;
import static java.util.regex.Pattern.*;
import static org.springframework.util.ObjectUtils.*;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -36,24 +39,8 @@ import org.springframework.util.StringUtils;
*/
class StringQuery {
private static final Pattern LIKE_PATTERN;
private static final String MESSAGE = "Already found like binding with same index / parameter name but differing binding type! Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same like binding.";
static {
StringBuilder builder = new StringBuilder();
builder.append("(?<=like)"); // starts with like
builder.append("(?: )+"); // some whitespace
builder.append("(");
builder.append("%?(\\?(\\d+))%?"); // position parameter with likes
builder.append("|"); // or
builder.append("%?(:(\\w+))%?"); // named parameter with likes;
builder.append(")");
LIKE_PATTERN = Pattern.compile(builder.toString(), CASE_INSENSITIVE);
}
private final String query;
private final List<StringQuery.LikeBinding> bindings;
private final List<ParameterBinding> bindings;
private final String alias;
/**
@@ -65,8 +52,9 @@ class StringQuery {
Assert.hasText(query, "Query must not be null or empty!");
this.bindings = new ArrayList<StringQuery.LikeBinding>();
this.query = parseLikeBindings(query);
this.bindings = new ArrayList<StringQuery.ParameterBinding>();
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings);
this.alias = QueryUtils.detectAlias(query);
}
@@ -75,16 +63,16 @@ class StringQuery {
*
* @return
*/
public boolean hasLikeBindings() {
public boolean hasParameterBindings() {
return !bindings.isEmpty();
}
/**
* Returns the {@link LikeBinding}s registered.
* Returns the {@link ParameterBinding}s registered.
*
* @return
*/
List<LikeBinding> getLikeBindings() {
List<ParameterBinding> getParameterBindings() {
return bindings;
}
@@ -107,14 +95,14 @@ class StringQuery {
}
/**
* Returns the {@link LikeBinding} for the given name.
* Returns the {@link ParameterBinding} for the given name.
*
* @param name
* @return
*/
public LikeBinding getBindingFor(String name) {
public ParameterBinding getBindingFor(String name) {
for (StringQuery.LikeBinding binding : bindings) {
for (StringQuery.ParameterBinding binding : bindings) {
if (binding.hasName(name)) {
return binding;
}
@@ -124,13 +112,13 @@ class StringQuery {
}
/**
* Returns the {@link LikeBinding} for the given position.
* Returns the {@link ParameterBinding} for the given position.
*
* @param position
* @return
*/
public LikeBinding getBindingFor(int position) {
for (LikeBinding binding : bindings) {
public ParameterBinding getBindingFor(int position) {
for (ParameterBinding binding : bindings) {
if (binding.hasPosition(position)) {
return binding;
}
@@ -140,124 +128,166 @@ class StringQuery {
}
/**
* Parses {@link LikeBinding} instances from the given query and adds them to the registered bindings. Returns the
* cleaned up query.
* A parser that extracts the parameter bindings from a given query string.
*
* @param query
* @return
* @author Thomas Darimont
*/
private final String parseLikeBindings(String query) {
static enum ParameterBindingParser {
Matcher matcher = LIKE_PATTERN.matcher(query);
String result = query;
INSTANCE;
while (matcher.find()) {
private static final Pattern PARAMETER_BINDING_PATTERN;
private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! "
+ "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding.";
Type likeType = getLikeTypeFrom(matcher.group(1));
String index = matcher.group(3);
String replacement = matcher.group(2);
static {
if (index != null) {
checkAndRegister(new LikeBinding(Integer.parseInt(index), likeType));
} else {
checkAndRegister(new LikeBinding(matcher.group(5), likeType));
replacement = matcher.group(4);
}
StringBuilder builder = new StringBuilder();
builder.append("(?<=(like|in))"); // parameterBindingType -> starts with "like" or "in"
builder.append("(?: )+"); // some whitespace
builder.append("(");
builder.append("%?(\\?(\\d+))%?"); // position parameter
builder.append("|"); // or
builder.append("%?(:(\\w+))%?"); // named parameter;
builder.append(")");
result = StringUtils.replace(result, matcher.group(1), replacement);
PARAMETER_BINDING_PATTERN = Pattern.compile(builder.toString(), CASE_INSENSITIVE);
}
return result;
}
/**
* Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns
* the cleaned up query.
*
* @param query
* @return
*/
private final String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query,
List<ParameterBinding> bindings) {
private final void checkAndRegister(LikeBinding binding) {
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(query);
String result = query;
for (LikeBinding existing : bindings) {
if (existing.hasName(binding.name) || existing.hasPosition(binding.position)) {
Assert.isTrue(existing.equals(binding), String.format(MESSAGE, existing, binding));
while (matcher.find()) {
String parameterIndexString = matcher.group(4);
String parameterName = parameterIndexString != null ? null : matcher.group(6);
Integer parameterIndex = parameterIndexString == null ? null : Integer.valueOf(parameterIndexString);
switch (ParameterBindingKind.of(matcher.group(1))) {
case LIKE:
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
String replacement = matcher.group(3);
if (parameterIndex != null) {
checkAndRegister(new LikeParameterBinding(parameterIndex, likeType), bindings);
} else {
checkAndRegister(new LikeParameterBinding(parameterName, likeType), bindings);
replacement = matcher.group(5);
}
result = StringUtils.replace(result, matcher.group(2), replacement);
break;
case IN:
if (parameterIndex != null) {
checkAndRegister(new InParameterBinding(parameterIndex), bindings);
} else {
checkAndRegister(new InParameterBinding(parameterName), bindings);
}
break;
case AS_IS: // fall-through we don't need a special parameter binding for the given parameter.
default:
;
}
}
return result;
}
this.bindings.add(binding);
private static void checkAndRegister(ParameterBinding binding, List<ParameterBinding> bindings) {
for (ParameterBinding existing : bindings) {
if (existing.hasName(binding.getName()) || existing.hasPosition(binding.getPosition())) {
Assert.isTrue(existing.equals(binding), String.format(MESSAGE, existing, binding));
}
}
bindings.add(binding);
}
/**
* An enum for the supported parameter binding kinds.
*
* @author Thomas Darimont
*/
enum ParameterBindingKind {
LIKE, IN, AS_IS;
/**
* Return the appropriate {@link ParameterBindingKind} for the given {@link String}. Returns {@value #AS_IS} in
* case no other {@link ParameterBindingKind} could be found.
*
* @param parameterBindingKindName
* @return
*/
static ParameterBindingKind of(String parameterBindingKindName) {
for (ParameterBindingKind type : values()) {
if (type.name().equalsIgnoreCase(parameterBindingKindName)) {
return type;
}
}
return AS_IS;
}
}
}
/**
* Extracts the like {@link Type} from the given JPA like expression.
* A generic parameter binding with name or position information.
*
* @param expression must not be {@literal null} or empty.
* @return
* @author Thomas Darimont
*/
private static Type getLikeTypeFrom(String expression) {
Assert.hasText(expression);
if (expression.matches("%.*%")) {
return Type.CONTAINING;
}
if (expression.startsWith("%")) {
return Type.ENDING_WITH;
}
if (expression.endsWith("%")) {
return Type.STARTING_WITH;
}
return Type.LIKE;
}
/**
* Represents a paramter binding in a JPQL query augmented with instructions of how to apply a parameter as LIKE
* parameter. This allows expressions like {@code …like %?1} in the JPQL query, which is not allowed by plain JPA.
*
* @author Oliver Gierke
*/
static class LikeBinding {
private static final List<Type> SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH,
Type.ENDING_WITH, Type.LIKE);
static class ParameterBinding {
private final String name;
private final Integer position;
private final Type type;
/**
* Creates a new {@link LikeBinding} for the parameter with the given name and {@link Type}.
* Creates a new {@link ParameterBinding} for the parameter with the given name.
*
* @param name must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @param name must not be {@literal null}.
*/
public LikeBinding(String name, Type type) {
public ParameterBinding(String name) {
Assert.hasText(name, "Name must not be null or empty!");
Assert.notNull(type, "Type must not be null!");
Assert.isTrue(SUPPORTED_TYPES.contains(type),
String.format("Type must be one of %s!", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
Assert.notNull(name, "Name must not be null!");
this.name = name;
this.type = type;
this.position = null;
}
/**
* Creates a new {@link LikeBinding} for the parameter with the given position and {@link Type}.
* Creates a new {@link ParameterBinding} for the parameter with the given position.
*
* @param position
* @param type must not be {@literal null}.
* @param position must not be {@literal null}.
*/
public LikeBinding(int position, Type type) {
public ParameterBinding(Integer position) {
Assert.isTrue(position > 0, "Position must be greater than zero!");
Assert.notNull(type, "Type must not be null!");
Assert.notNull(position, "Position must not be null!");
this.position = position;
this.type = type;
this.name = null;
this.position = position;
}
/**
* Returns whether the binding has the given name. Will always be {@literal false} in case the {@link LikeBinding}
* has been set up from a position.
* Returns whether the binding has the given name. Will always be {@literal false} in case the
* {@link ParameterBinding} has been set up from a position.
*
* @param name
* @return
@@ -268,7 +298,7 @@ class StringQuery {
/**
* Returns whether the binding has the given position. Will always be {@literal false} in case the
* {@link LikeBinding} has been set up from a name.
* {@link ParameterBinding} has been set up from a name.
*
* @param position
* @return
@@ -278,7 +308,171 @@ class StringQuery {
}
/**
* Returns the type of the {@link LikeBinding}.
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the position
*/
public Integer getPosition() {
return position;
}
@Override
public int hashCode() {
int result = 17;
result += nullSafeHashCode(this.name);
result += nullSafeHashCode(this.position);
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ParameterBinding)) {
return false;
}
ParameterBinding that = (ParameterBinding) obj;
return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("ParameterBinding [name: %s, position: %d]", getName(), getPosition());
}
/**
* @param valueToBind
* @return
*/
public Object prepare(Object valueToBind) {
return valueToBind;
}
}
/**
* Represents a {@link ParameterBinding} in a JPQL query augmented with instructions of how to apply a parameter as an
* {@code IN} parameter.
*
* @author Thomas Darimont
*/
static class InParameterBinding extends ParameterBinding {
/**
* Creates a new {@link InParameterBinding} for the parameter with the given name.
*
* @param name
*/
public InParameterBinding(String name) {
super(name);
}
/**
* Creates a new {@link InParameterBinding} for the parameter with the given position.
*
* @param position
*/
public InParameterBinding(int position) {
super(position);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding#prepare(java.lang.Object)
*/
@Override
public Object prepare(Object valueToBind) {
return convertArrayToCollectionIfNecessary(valueToBind);
}
/**
* Returns the given value as collection if it is an array or as is if not.
*
* @return
*/
private Object convertArrayToCollectionIfNecessary(Object value) {
if (!ObjectUtils.isArray(value)) {
return value;
}
int length = Array.getLength(value);
Collection<Object> result = new ArrayList<Object>(length);
for (int i = 0; i < length; i++) {
result.add(Array.get(value, i));
}
return result;
}
}
/**
* Represents a parameter binding in a JPQL query augmented with instructions of how to apply a parameter as LIKE
* parameter. This allows expressions like {@code …like %?1} in the JPQL query, which is not allowed by plain JPA.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
static class LikeParameterBinding extends ParameterBinding {
private static final List<Type> SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH,
Type.ENDING_WITH, Type.LIKE);
private final Type type;
/**
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type}.
*
* @param name must not be {@literal null} or empty.
* @param type must not be {@literal null}.
*/
public LikeParameterBinding(String name, Type type) {
super(name);
Assert.hasText(name, "Name must not be null or empty!");
Assert.notNull(type, "Type must not be null!");
Assert.isTrue(SUPPORTED_TYPES.contains(type),
String.format("Type must be one of %s!", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
this.type = type;
}
/**
* Creates a new {@link LikeParameterBinding} for the parameter with the given position and {@link Type}.
*
* @param position
* @param type must not be {@literal null}.
*/
public LikeParameterBinding(int position, Type type) {
super(position);
Assert.isTrue(position > 0, "Position must be greater than zero!");
Assert.notNull(type, "Type must not be null!");
Assert.isTrue(SUPPORTED_TYPES.contains(type),
String.format("Type must be one of %s!", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
this.type = type;
}
/**
* Returns the type of the {@link LikeParameterBinding}.
*
* @return
*/
@@ -317,14 +511,13 @@ class StringQuery {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LikeBinding)) {
if (!(obj instanceof LikeParameterBinding)) {
return false;
}
LikeBinding that = (LikeBinding) obj;
LikeParameterBinding that = (LikeParameterBinding) obj;
return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position)
&& this.type.equals(that.type);
return super.equals(obj) && this.type.equals(that.type);
}
/*
@@ -334,10 +527,8 @@ class StringQuery {
@Override
public int hashCode() {
int result = 17;
int result = super.hashCode();
result += nullSafeHashCode(this.name);
result += nullSafeHashCode(this.position);
result += nullSafeHashCode(this.type);
return result;
@@ -349,7 +540,32 @@ class StringQuery {
*/
@Override
public String toString() {
return String.format("LikeBinding [name: %s, position: %d, type: %s]", name, position, type);
return String.format("LikeBinding [name: %s, position: %d, type: %s]", getName(), getPosition(), type);
}
/**
* Extracts the like {@link Type} from the given JPA like expression.
*
* @param expression must not be {@literal null} or empty.
* @return
*/
private static Type getLikeTypeFrom(String expression) {
Assert.hasText(expression);
if (expression.matches("%.*%")) {
return Type.CONTAINING;
}
if (expression.startsWith("%")) {
return Type.ENDING_WITH;
}
if (expression.endsWith("%")) {
return Type.STARTING_WITH;
}
return Type.LIKE;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,14 @@ package org.springframework.data.jpa.repository.query;
import javax.persistence.Query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.util.Assert;
/**
* {@link ParameterBinder} that takes {@link LikeBinding}s encapsulated in a {@link StringQuery} into account.
* {@link ParameterBinder} that takes {@link LikeParameterBinding}s encapsulated in a {@link StringQuery} into account.
*
* @author Oliver Gierke
* @author Thomas Darimont
@@ -58,9 +59,9 @@ public class StringQueryParameterBinder extends ParameterBinder {
Object valueToBind = value;
if (query.hasLikeBindings()) {
if (query.hasParameterBindings()) {
LikeBinding binding = getBindingFor(jpaQuery, position, methodParameter);
ParameterBinding binding = getBindingFor(jpaQuery, position, methodParameter);
if (binding != null) {
valueToBind = binding.prepare(valueToBind);
@@ -71,14 +72,14 @@ public class StringQueryParameterBinder extends ParameterBinder {
}
/**
* Finds the {@link LikeBinding} to be applied before binding a parameter value to the query.
* Finds the {@link LikeParameterBinding} to be applied before binding a parameter value to the query.
*
* @param jpaQuery must not be {@literal null}.
* @param position
* @param methodParameter must not be {@literal null}.
* @return the {@link LikeBinding} for the given parameters or {@literal null} if none available.
* @return the {@link LikeParameterBinding} for the given parameters or {@literal null} if none available.
*/
private LikeBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) {
private ParameterBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) {
try {

View File

@@ -1212,6 +1212,34 @@ public class UserRepositoryTests {
assertThat(result, hasItem(firstUser));
}
/**
* @DATAJPA-461
*/
@Test
public void customFindByQueryWithPositionalVarargsParameters() {
flushTestUsers();
Collection<User> result = repository.findByIdsCustomWithPositionalVarArgs(firstUser.getId(), secondUser.getId());
assertThat(result, hasSize(2));
assertThat(result, hasItems(firstUser, secondUser));
}
/**
* @DATAJPA-461
*/
@Test
public void customFindByQueryWithNamedVarargsParameters() {
flushTestUsers();
Collection<User> result = repository.findByIdsCustomWithNamedVarArgs(firstUser.getId(), secondUser.getId());
assertThat(result, hasSize(2));
assertThat(result, hasItems(firstUser, secondUser));
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -19,7 +19,7 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
import org.springframework.data.repository.query.parser.Part.Type;
/**
@@ -29,33 +29,33 @@ public class LikeBindingUnitTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsNullName() {
new LikeBinding(null, Type.CONTAINING);
new LikeParameterBinding(null, Type.CONTAINING);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsEmptyName() {
new LikeBinding("", Type.CONTAINING);
new LikeParameterBinding("", Type.CONTAINING);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullType() {
new LikeBinding("foo", null);
new LikeParameterBinding("foo", null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidType() {
new LikeBinding("foo", Type.SIMPLE_PROPERTY);
new LikeParameterBinding("foo", Type.SIMPLE_PROPERTY);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidPosition() {
new LikeBinding(0, Type.CONTAINING);
new LikeParameterBinding(0, Type.CONTAINING);
}
@Test
public void setsUpInstanceForName() {
LikeBinding binding = new LikeBinding("foo", Type.CONTAINING);
LikeParameterBinding binding = new LikeParameterBinding("foo", Type.CONTAINING);
assertThat(binding.hasName("foo"), is(true));
assertThat(binding.hasName("bar"), is(false));
@@ -67,7 +67,7 @@ public class LikeBindingUnitTests {
@Test
public void setsUpInstanceForIndex() {
LikeBinding binding = new LikeBinding(1, Type.CONTAINING);
LikeParameterBinding binding = new LikeParameterBinding(1, Type.CONTAINING);
assertThat(binding.hasName("foo"), is(false));
assertThat(binding.hasName(null), is(false));
@@ -83,12 +83,12 @@ public class LikeBindingUnitTests {
assertAugmentedValue(Type.ENDING_WITH, "%value");
assertAugmentedValue(Type.STARTING_WITH, "value%");
assertThat(new LikeBinding(1, Type.CONTAINING).prepare(null), is(nullValue()));
assertThat(new LikeParameterBinding(1, Type.CONTAINING).prepare(null), is(nullValue()));
}
private static void assertAugmentedValue(Type type, Object value) {
LikeBinding binding = new LikeBinding("foo", type);
LikeParameterBinding binding = new LikeParameterBinding("foo", type);
assertThat(binding.prepare("value"), is(value));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2013 the original author or authors.
* Copyright 2008-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -223,18 +222,18 @@ public class ParameterBinderUnitTests {
}
/**
* @see DATAJPA-415
* @see DATAJPA-461
* @throws Exception
*/
@Test
public void shouldAllowBindingOfVarArgs() throws Exception {
public void shouldAllowBindingOfVarArgsAsIs() throws Exception {
Method method = SampleRepository.class.getMethod("validWithVarArgs", Integer[].class);
JpaParameters parameters = new JpaParameters(method);
Integer[] ids = new Integer[] { 1, 2, 3 };
new ParameterBinder(parameters, new Object[] { ids }).bind(query);
verify(query).setParameter(eq(1), eq(Arrays.asList(1, 2, 3)));
verify(query).setParameter(eq(1), eq(ids));
}
public SampleEntity findByEmbeddable(SampleEmbeddable embeddable) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,9 @@ import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
import org.springframework.data.jpa.repository.query.StringQuery.InParameterBinding;
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.parser.Part.Type;
/**
@@ -41,13 +43,13 @@ public class StringQueryUnitTests {
String source = "select from User u where u.firstname like :firstname";
StringQuery query = new StringQuery(source);
assertThat(query.hasLikeBindings(), is(true));
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is(source));
List<LikeBinding> bindings = query.getLikeBindings();
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(1));
LikeBinding binding = bindings.get(0);
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
assertThat(binding.getType(), is(Type.LIKE));
assertThat(binding.hasName("firstname"), is(true));
}
@@ -57,18 +59,18 @@ public class StringQueryUnitTests {
StringQuery query = new StringQuery("select u from User u where u.firstname like %?1% or u.lastname like %?2");
assertThat(query.hasLikeBindings(), is(true));
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is("select u from User u where u.firstname like ?1 or u.lastname like ?2"));
List<LikeBinding> bindings = query.getLikeBindings();
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(2));
LikeBinding binding = bindings.get(0);
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
assertThat(binding, is(notNullValue()));
assertThat(binding.hasPosition(1), is(true));
assertThat(binding.getType(), is(Type.CONTAINING));
binding = bindings.get(1);
binding = (LikeParameterBinding) bindings.get(1);
assertThat(binding, is(notNullValue()));
assertThat(binding.hasPosition(2), is(true));
assertThat(binding.getType(), is(Type.ENDING_WITH));
@@ -79,18 +81,92 @@ public class StringQueryUnitTests {
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname");
assertThat(query.hasLikeBindings(), is(true));
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is("select u from User u where u.firstname like :firstname"));
List<LikeBinding> bindings = query.getLikeBindings();
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(1));
LikeBinding binding = bindings.get(0);
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
assertThat(binding, is(notNullValue()));
assertThat(binding.hasName("firstname"), is(true));
assertThat(binding.getType(), is(Type.ENDING_WITH));
}
/**
* @see DATAJPA-461
*/
@Test
public void detectsNamedInParameterBindings() {
String queryString = "select u from User u where u.id in :ids";
StringQuery query = new StringQuery(queryString);
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is(queryString));
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(1));
assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0));
}
/**
* @see DATAJPA-461
*/
@Test
public void detectsMultipleNamedInParameterBindings() {
String queryString = "select u from User u where u.id in :ids and u.name in :names and foo = :bar";
StringQuery query = new StringQuery(queryString);
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is(queryString));
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(2));
assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0));
assertNamedBinding(InParameterBinding.class, "names", bindings.get(1));
}
/**
* @see DATAJPA-461
*/
@Test
public void detectsPositionalInParameterBindings() {
String queryString = "select u from User u where u.id in ?1";
StringQuery query = new StringQuery(queryString);
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is(queryString));
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(1));
assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0));
}
/**
* @see DATAJPA-461
*/
@Test
public void detectsMultiplePositionalInParameterBindings() {
String queryString = "select u from User u where u.id in ?1 and u.names in ?2 and foo = ?3";
StringQuery query = new StringQuery(queryString);
assertThat(query.hasParameterBindings(), is(true));
assertThat(query.getQueryString(), is(queryString));
List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings, hasSize(2));
assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0));
assertPositionalBinding(InParameterBinding.class, 2, bindings.get(1));
}
/**
* @see DATAJPA-373
*/
@@ -103,4 +179,20 @@ public class StringQueryUnitTests {
public void rejectsDifferentBindingsForRepeatedParameter() {
new StringQuery("select u from User u where u.firstname like %?1 and u.lastname like ?1%");
}
private void assertPositionalBinding(Class<? extends ParameterBinding> bindingType, Integer position,
ParameterBinding expectedBinding) {
assertThat(bindingType.isInstance(expectedBinding), is(true));
assertThat(expectedBinding, is(notNullValue()));
assertThat(expectedBinding.hasPosition(position), is(true));
}
private void assertNamedBinding(Class<? extends ParameterBinding> bindingType, String parameterName,
ParameterBinding expectedBinding) {
assertThat(bindingType.isInstance(expectedBinding), is(true));
assertThat(expectedBinding, is(notNullValue()));
assertThat(expectedBinding.hasName(parameterName), is(true));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2013 the original author or authors.
* Copyright 2008-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -277,6 +277,18 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
*/
Collection<User> findByIdIn(@Param("ids") Integer... ids);
/**
* @see DATAJPA-461
*/
@Query("select u from User u where u.id in ?1")
Collection<User> findByIdsCustomWithPositionalVarArgs(Integer... ids);
/**
* @see DATAJPA-461
*/
@Query("select u from User u where u.id in :ids")
Collection<User> findByIdsCustomWithNamedVarArgs(@Param("ids") Integer... ids);
/**
* @see DATAJPA-415
*/