Browse Source

DATAMONGO-1565 - Polishing.

Consider quoted/unquoted parameter use with the same parameter reference. Extend date range in license headers.
pull/410/merge
Mark Paluch 9 years ago committed by Oliver Gierke
parent
commit
712d8be7bb
  1. 52
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java
  2. 70
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java

52
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2015 the original author or authors. * Copyright 2015-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,6 +15,9 @@
*/ */
package org.springframework.data.mongodb.repository.query; package org.springframework.data.mongodb.repository.query;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -44,6 +47,7 @@ import com.mongodb.util.JSON;
* @author Christoph Strobl * @author Christoph Strobl
* @author Thomas Darimont * @author Thomas Darimont
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch
* @since 1.9 * @since 1.9
*/ */
class ExpressionEvaluatingParameterBinder { class ExpressionEvaluatingParameterBinder {
@ -90,7 +94,7 @@ class ExpressionEvaluatingParameterBinder {
* *
* @param input must not be {@literal null} or empty. * @param input must not be {@literal null} or empty.
* @param accessor must not be {@literal null}. * @param accessor must not be {@literal null}.
* @param bindings must not be {@literal null}. * @param bindingContext must not be {@literal null}.
* @return * @return
*/ */
private String replacePlaceholders(String input, MongoParameterAccessor accessor, BindingContext bindingContext) { private String replacePlaceholders(String input, MongoParameterAccessor accessor, BindingContext bindingContext) {
@ -232,22 +236,23 @@ class ExpressionEvaluatingParameterBinder {
* @param groupName The actual {@link Matcher#group() group}. * @param groupName The actual {@link Matcher#group() group}.
* @return * @return
*/ */
private String extractPlaceholder(String groupName) { private Placeholder extractPlaceholder(String groupName) {
if (!groupName.endsWith("'") && !groupName.endsWith("\"")) { if (!groupName.endsWith("'") && !groupName.endsWith("\"")) {
return groupName; return new Placeholder(groupName, false);
} }
return groupName.substring(0, groupName.length() - 1); return new Placeholder(groupName.substring(0, groupName.length() - 1), true);
} }
/** /**
* @author Christoph Strobl * @author Christoph Strobl
* @author Mark Paluch
* @since 1.9 * @since 1.9
*/ */
static class BindingContext { static class BindingContext {
final MongoParameters parameters; final MongoParameters parameters;
final Map<String, ParameterBinding> bindings; final Map<Placeholder, ParameterBinding> bindings;
/** /**
* Creates new {@link BindingContext}. * Creates new {@link BindingContext}.
@ -279,13 +284,13 @@ class ExpressionEvaluatingParameterBinder {
/** /**
* Get the concrete {@link ParameterBinding} for a given {@literal placeholder}. * Get the concrete {@link ParameterBinding} for a given {@literal placeholder}.
* *
* @param placeholder must not be {@literal null}. * @param placeholder must not be {@literal null}.
* @return * @return
* @throws java.util.NoSuchElementException * @throws java.util.NoSuchElementException
* @since 1.10 * @since 1.10
*/ */
ParameterBinding getBindingFor(String placeholder) { ParameterBinding getBindingFor(Placeholder placeholder) {
if (!bindings.containsKey(placeholder)) { if (!bindings.containsKey(placeholder)) {
throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder)); throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder));
@ -296,20 +301,43 @@ class ExpressionEvaluatingParameterBinder {
/** /**
* Get the associated {@link MongoParameters}. * Get the associated {@link MongoParameters}.
* *
* @return * @return
*/ */
public MongoParameters getParameters() { public MongoParameters getParameters() {
return parameters; return parameters;
} }
private static Map<String, ParameterBinding> mapBindings(List<ParameterBinding> bindings) { private static Map<Placeholder, ParameterBinding> mapBindings(List<ParameterBinding> bindings) {
Map<String, ParameterBinding> map = new LinkedHashMap<String, ParameterBinding>(bindings.size(), 1); Map<Placeholder, ParameterBinding> map = new LinkedHashMap<Placeholder, ParameterBinding>(bindings.size(), 1);
for (ParameterBinding binding : bindings) { for (ParameterBinding binding : bindings) {
map.put(binding.getParameter(), binding); map.put(new Placeholder(binding.getParameter(), binding.isQuoted()), binding);
} }
return map; return map;
} }
} }
/**
* Encapsulates a quoted/unquoted parameter placeholder.
*
* @author Mark Paluch
* @since 1.9
*/
@Data
@RequiredArgsConstructor
static class Placeholder {
private final String parameter;
private final boolean quoted;
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return quoted ? String.format("'%s'", parameter) : parameter;
}
}
} }

70
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2015 the original author or authors. * Copyright 2011-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -50,6 +50,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder; import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@ -85,9 +86,9 @@ public class StringBasedMongoQueryUnitTests {
public void bindsSimplePropertyCorrectly() throws Exception { public void bindsSimplePropertyCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class);
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -99,13 +100,13 @@ public class StringBasedMongoQueryUnitTests {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAddress", Address.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAddress", Address.class);
Address address = new Address("Foo", "0123", "Bar"); Address address = new Address("Foo", "0123", "Bar");
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, address); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, address);
DBObject dbObject = new BasicDBObject(); DBObject dbObject = new BasicDBObject();
converter.write(address, dbObject); converter.write(address, dbObject);
dbObject.removeField(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY); dbObject.removeField(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
BasicDBObject queryObject = new BasicDBObject("address", dbObject); BasicDBObject queryObject = new BasicDBObject("address", dbObject);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject);
@ -118,7 +119,7 @@ public class StringBasedMongoQueryUnitTests {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAndAddress", String.class, Address.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAndAddress", String.class, Address.class);
Address address = new Address("Foo", "0123", "Bar"); Address address = new Address("Foo", "0123", "Bar");
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews", address); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews", address);
DBObject addressDbObject = new BasicDBObject(); DBObject addressDbObject = new BasicDBObject();
converter.write(address, addressDbObject); converter.write(address, addressDbObject);
@ -127,7 +128,7 @@ public class StringBasedMongoQueryUnitTests {
DBObject reference = new BasicDBObject("address", addressDbObject); DBObject reference = new BasicDBObject("address", addressDbObject);
reference.put("lastname", "Matthews"); reference.put("lastname", "Matthews");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), is(reference)); assertThat(query.getQueryObject(), is(reference));
} }
@ -228,10 +229,10 @@ public class StringBasedMongoQueryUnitTests {
@Test @Test
public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -243,10 +244,10 @@ public class StringBasedMongoQueryUnitTests {
@Test @Test
public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception { public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -259,9 +260,9 @@ public class StringBasedMongoQueryUnitTests {
public void bindsSimplePropertyWithRegexCorrectly() throws Exception { public void bindsSimplePropertyWithRegexCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class);
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -304,10 +305,10 @@ public class StringBasedMongoQueryUnitTests {
@Test @Test
public void shouldSupportExpressionsInCustomQueries() throws Exception { public void shouldSupportExpressionsInCustomQueries() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -319,11 +320,11 @@ public class StringBasedMongoQueryUnitTests {
@Test @Test
public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class,
String.class); String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{ \"id\" : { \"$exists\" : true}}"); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{ \"id\" : { \"$exists\" : true}}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
@ -335,11 +336,11 @@ public class StringBasedMongoQueryUnitTests {
@Test @Test
public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception { public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndMultipleNestedObjects", StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndMultipleNestedObjects",
boolean.class, String.class, String.class); boolean.class, String.class, String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery( org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
"{ \"id\" : { \"$exists\" : true} , \"foo\" : 42 , \"bar\" : { \"$exists\" : false}}"); "{ \"id\" : { \"$exists\" : true} , \"foo\" : 42 , \"bar\" : { \"$exists\" : false}}");
@ -353,28 +354,48 @@ public class StringBasedMongoQueryUnitTests {
public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception {
byte[] binaryData = "Matthews".getBytes("UTF-8"); byte[] binaryData = "Matthews".getBytes("UTF-8");
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, binaryData); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, binaryData);
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinary", byte[].class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinary", byte[].class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { '$binary' : '" org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { '$binary' : '"
+ DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : " + BSON.B_GENERAL + "}}"); + DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : " + BSON.B_GENERAL + "}}");
assertThat(query.getQueryObject(), is(reference.getQueryObject())); assertThat(query.getQueryObject(), is(reference.getQueryObject()));
} }
/**
* @see DATAMONGO-1565
*/
@Test
public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAgeQuotedAndUnquoted", Integer.TYPE);
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 3);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
BasicDBList or = new BasicDBList();
or.add(new BasicDBObject("age", 3));
or.add(new BasicDBObject("displayAge", "3"));
BasicDBObject queryObject = new BasicDBObject("$or", or);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject);
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
/** /**
* @see DATAMONGO-1565 * @see DATAMONGO-1565
*/ */
@Test @Test
public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "argWith?1andText",
"nothing-special"); "nothing-special");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), assertThat(query.getQueryObject(),
is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}")));
} }
@ -512,6 +533,9 @@ public class StringBasedMongoQueryUnitTests {
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
List<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); List<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
@Query(value = "{ $or : [{'age' : ?0 }, {'displayAge' : '?0'}] }")
boolean findByAgeQuotedAndUnquoted(int age);
@Query("{ 'arg0' : ?0, 'arg1' : ?1 }") @Query("{ 'arg0' : ?0, 'arg1' : ?1 }")
List<Person> findByStringWithWildcardChar(String arg0, String arg1); List<Person> findByStringWithWildcardChar(String arg0, String arg1);
} }

Loading…
Cancel
Save