From f8b0917c90115986ff30d2cbbc55299b6342cbeb Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 10 Feb 2014 15:53:18 +0100 Subject: [PATCH] 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. --- .../jpa/repository/query/ParameterBinder.java | 29 +- .../jpa/repository/query/StringQuery.java | 448 +++++++++++++----- .../query/StringQueryParameterBinder.java | 17 +- .../jpa/repository/UserRepositoryTests.java | 28 ++ .../query/LikeBindingUnitTests.java | 20 +- .../query/ParameterBinderUnitTests.java | 9 +- .../query/StringQueryUnitTests.java | 116 ++++- .../jpa/repository/sample/UserRepository.java | 14 +- 8 files changed, 502 insertions(+), 179 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index a1afa8c60..1341f4e65 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -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 result = new ArrayList(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). * diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 8153376cf..497206881 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -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 bindings; + private final List 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(); - this.query = parseLikeBindings(query); + this.bindings = new ArrayList(); + 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 getLikeBindings() { + List 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 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 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 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 result = new ArrayList(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 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; } } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java index 7c1228bd0..b5c61d614 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQueryParameterBinder.java @@ -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 { diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index cdb4b784c..ad00f68cb 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1212,6 +1212,34 @@ public class UserRepositoryTests { assertThat(result, hasItem(firstUser)); } + /** + * @DATAJPA-461 + */ + @Test + public void customFindByQueryWithPositionalVarargsParameters() { + + flushTestUsers(); + + Collection result = repository.findByIdsCustomWithPositionalVarArgs(firstUser.getId(), secondUser.getId()); + + assertThat(result, hasSize(2)); + assertThat(result, hasItems(firstUser, secondUser)); + } + + /** + * @DATAJPA-461 + */ + @Test + public void customFindByQueryWithNamedVarargsParameters() { + + flushTestUsers(); + + Collection result = repository.findByIdsCustomWithNamedVarArgs(firstUser.getId(), secondUser.getId()); + + assertThat(result, hasSize(2)); + assertThat(result, hasItems(firstUser, secondUser)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java index 1fbb5ea56..248eddf83 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java @@ -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)); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java index 6d1285600..36148b229 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBinderUnitTests.java @@ -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) { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index 27a3794a9..9373d625b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -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 bindings = query.getLikeBindings(); + List 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 bindings = query.getLikeBindings(); + List 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 bindings = query.getLikeBindings(); + List 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 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 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 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 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 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 bindingType, String parameterName, + ParameterBinding expectedBinding) { + + assertThat(bindingType.isInstance(expectedBinding), is(true)); + assertThat(expectedBinding, is(notNullValue())); + assertThat(expectedBinding.hasName(parameterName), is(true)); + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 42f18dc0f..5510f80f8 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -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, JpaSpecifi */ Collection findByIdIn(@Param("ids") Integer... ids); + /** + * @see DATAJPA-461 + */ + @Query("select u from User u where u.id in ?1") + Collection findByIdsCustomWithPositionalVarArgs(Integer... ids); + + /** + * @see DATAJPA-461 + */ + @Query("select u from User u where u.id in :ids") + Collection findByIdsCustomWithNamedVarArgs(@Param("ids") Integer... ids); + /** * @see DATAJPA-415 */