Browse Source
An explorative approach to QBE trying find possibilities and limitations. We now support querying documents by providing a sample of the given object holding compare values. For the sake of partial matching we flatten out nested structures so we can create different queries for matching like:
{ _id : 1, nested : { value : "conflux" } }
{ _id : 1, nested.value : { "conflux" } }
This is useful when you want so search using a only partially filled nested document. String matching can be configured to wrap strings with $regex which creates { firstname : { $regex : "^foo", $options: "i" } } when using StringMatchMode.STARTING along with the ignoreCaseOption. DBRefs and geo structures such as Point or GeoJsonPoint is converted to their according structure.
Related tickets: DATACMNS-810.
Original pull request: #341.
pull/346/merge
23 changed files with 1730 additions and 161 deletions
@ -0,0 +1,229 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2015 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. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.mongodb.core.convert; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Stack; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import org.springframework.data.domain.Example; |
||||||
|
import org.springframework.data.domain.Example.NullHandler; |
||||||
|
import org.springframework.data.domain.Example.StringMatcher; |
||||||
|
import org.springframework.data.domain.PropertySpecifier; |
||||||
|
import org.springframework.data.mapping.PropertyHandler; |
||||||
|
import org.springframework.data.mapping.context.MappingContext; |
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; |
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; |
||||||
|
import org.springframework.data.mongodb.core.query.MongoRegexCreator; |
||||||
|
import org.springframework.data.mongodb.core.query.SerializationUtils; |
||||||
|
import org.springframework.util.ObjectUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
import com.mongodb.BasicDBObject; |
||||||
|
import com.mongodb.DBObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Christoph Strobl |
||||||
|
* @since 1.8 |
||||||
|
*/ |
||||||
|
public class MongoExampleMapper { |
||||||
|
|
||||||
|
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext; |
||||||
|
private final MongoConverter converter; |
||||||
|
|
||||||
|
public MongoExampleMapper(MongoConverter converter) { |
||||||
|
|
||||||
|
this.converter = converter; |
||||||
|
this.mappingContext = converter.getMappingContext(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from |
||||||
|
* {@link Example#getProbe()}. |
||||||
|
* |
||||||
|
* @param example |
||||||
|
* @return |
||||||
|
* @since 1.8 |
||||||
|
*/ |
||||||
|
public DBObject getMappedExample(Example<?> example) { |
||||||
|
return getMappedExample(example, mappingContext.getPersistentEntity(example.getSampleType())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from |
||||||
|
* {@link Example#getProbe()}. |
||||||
|
* |
||||||
|
* @param example |
||||||
|
* @param entity |
||||||
|
* @return |
||||||
|
* @since 1.8 |
||||||
|
*/ |
||||||
|
public DBObject getMappedExample(Example<?> example, MongoPersistentEntity<?> entity) { |
||||||
|
|
||||||
|
DBObject reference = (DBObject) converter.convertToMongoType(example.getSampleObject()); |
||||||
|
|
||||||
|
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getSampleObject()).getIdentifier() == null) { |
||||||
|
reference.removeField(entity.getIdProperty().getFieldName()); |
||||||
|
} |
||||||
|
|
||||||
|
applyPropertySpecs("", reference, example); |
||||||
|
|
||||||
|
return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, example.getNullHandler()) ? reference : new BasicDBObject( |
||||||
|
SerializationUtils.flatMap(reference)); |
||||||
|
} |
||||||
|
|
||||||
|
private String getMappedPropertyPath(String path, Example<?> example) { |
||||||
|
|
||||||
|
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(example.getSampleType()); |
||||||
|
|
||||||
|
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator(); |
||||||
|
|
||||||
|
final Stack<MongoPersistentProperty> stack = new Stack<MongoPersistentProperty>(); |
||||||
|
|
||||||
|
List<String> resultParts = new ArrayList<String>(); |
||||||
|
|
||||||
|
while (parts.hasNext()) { |
||||||
|
|
||||||
|
final String part = parts.next(); |
||||||
|
MongoPersistentProperty prop = entity.getPersistentProperty(part); |
||||||
|
|
||||||
|
if (prop == null) { |
||||||
|
|
||||||
|
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void doWithPersistentProperty(MongoPersistentProperty property) { |
||||||
|
|
||||||
|
if (property.getFieldName().equals(part)) { |
||||||
|
stack.push(property); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if (stack.isEmpty()) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
prop = stack.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
resultParts.add(prop.getName()); |
||||||
|
|
||||||
|
if (prop.isEntity() && mappingContext.hasPersistentEntityFor(prop.getActualType())) { |
||||||
|
entity = mappingContext.getPersistentEntity(prop.getActualType()); |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return StringUtils.collectionToDelimitedString(resultParts, "."); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private void applyPropertySpecs(String path, DBObject source, Example<?> example) { |
||||||
|
|
||||||
|
if (!(source instanceof BasicDBObject)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Iterator<Map.Entry<String, Object>> iter = ((BasicDBObject) source).entrySet().iterator(); |
||||||
|
|
||||||
|
while (iter.hasNext()) { |
||||||
|
|
||||||
|
Map.Entry<String, Object> entry = iter.next(); |
||||||
|
|
||||||
|
if (entry.getKey().equals("_id") && entry.getValue() == null) { |
||||||
|
iter.remove(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey(); |
||||||
|
|
||||||
|
String mappedPropertyPath = getMappedPropertyPath(propertyPath, example); |
||||||
|
if (example.isIgnoredPath(propertyPath) || example.isIgnoredPath(mappedPropertyPath)) { |
||||||
|
iter.remove(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
PropertySpecifier specifier = null; |
||||||
|
StringMatcher stringMatcher = example.getDefaultStringMatcher(); |
||||||
|
Object value = entry.getValue(); |
||||||
|
boolean ignoreCase = example.isIngnoreCaseEnabled(); |
||||||
|
|
||||||
|
if (example.hasPropertySpecifiers()) { |
||||||
|
|
||||||
|
mappedPropertyPath = example.hasPropertySpecifier(propertyPath) ? propertyPath : getMappedPropertyPath( |
||||||
|
propertyPath, example); |
||||||
|
|
||||||
|
specifier = example.getPropertySpecifier(mappedPropertyPath); |
||||||
|
|
||||||
|
if (specifier != null) { |
||||||
|
if (specifier.hasStringMatcher()) { |
||||||
|
stringMatcher = specifier.getStringMatcher(); |
||||||
|
} |
||||||
|
if (specifier.getIgnoreCase() != null) { |
||||||
|
ignoreCase = specifier.getIgnoreCase(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: should a PropertySpecifier outrule the later on string matching?
|
||||||
|
if (specifier != null) { |
||||||
|
|
||||||
|
value = specifier.transformValue(value); |
||||||
|
if (value == null) { |
||||||
|
iter.remove(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
entry.setValue(value); |
||||||
|
} |
||||||
|
|
||||||
|
if (entry.getValue() instanceof String) { |
||||||
|
applyStringMatcher(entry, stringMatcher, ignoreCase); |
||||||
|
} else if (entry.getValue() instanceof BasicDBObject) { |
||||||
|
applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), example); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher stringMatcher, boolean ignoreCase) { |
||||||
|
|
||||||
|
BasicDBObject dbo = new BasicDBObject(); |
||||||
|
|
||||||
|
if (ObjectUtils.nullSafeEquals(StringMatcher.DEFAULT, stringMatcher)) { |
||||||
|
|
||||||
|
if (ignoreCase) { |
||||||
|
dbo.put("$regex", Pattern.quote((String) entry.getValue())); |
||||||
|
entry.setValue(dbo); |
||||||
|
} |
||||||
|
} else { |
||||||
|
|
||||||
|
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), |
||||||
|
stringMatcher.getPartType()); |
||||||
|
dbo.put("$regex", expression); |
||||||
|
entry.setValue(dbo); |
||||||
|
} |
||||||
|
|
||||||
|
if (ignoreCase) { |
||||||
|
dbo.put("$options", "i"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,101 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2015 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. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.mongodb.core.query; |
||||||
|
|
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import org.springframework.data.repository.query.parser.Part.Type; |
||||||
|
import org.springframework.util.ObjectUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Christoph Strobl |
||||||
|
* @since 1.8 |
||||||
|
*/ |
||||||
|
public enum MongoRegexCreator { |
||||||
|
|
||||||
|
INSTANCE; |
||||||
|
|
||||||
|
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}"); |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a regular expression String to be used with {@code $regex}. |
||||||
|
* |
||||||
|
* @param source the plain String |
||||||
|
* @param type |
||||||
|
* @return {@literal source} when {@literal source} or {@literal type} is {@literal null}. |
||||||
|
*/ |
||||||
|
public String toRegularExpression(String source, Type type) { |
||||||
|
|
||||||
|
if (type == null || source == null) { |
||||||
|
return source; |
||||||
|
} |
||||||
|
|
||||||
|
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, type); |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case STARTING_WITH: |
||||||
|
regex = "^" + regex; |
||||||
|
break; |
||||||
|
case ENDING_WITH: |
||||||
|
regex = regex + "$"; |
||||||
|
break; |
||||||
|
case CONTAINING: |
||||||
|
case NOT_CONTAINING: |
||||||
|
regex = ".*" + regex + ".*"; |
||||||
|
break; |
||||||
|
case SIMPLE_PROPERTY: |
||||||
|
case NEGATING_SIMPLE_PROPERTY: |
||||||
|
regex = "^" + regex + "$"; |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
return regex; |
||||||
|
} |
||||||
|
|
||||||
|
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) { |
||||||
|
|
||||||
|
if (!ObjectUtils.nullSafeEquals(Type.LIKE, type)) { |
||||||
|
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source; |
||||||
|
} |
||||||
|
|
||||||
|
if (source.equals("*")) { |
||||||
|
return ".*"; |
||||||
|
} |
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
|
||||||
|
boolean leadingWildcard = source.startsWith("*"); |
||||||
|
boolean trailingWildcard = source.endsWith("*"); |
||||||
|
|
||||||
|
String valueToUse = source.substring(leadingWildcard ? 1 : 0, |
||||||
|
trailingWildcard ? source.length() - 1 : source.length()); |
||||||
|
|
||||||
|
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) { |
||||||
|
valueToUse = Pattern.quote(valueToUse); |
||||||
|
} |
||||||
|
|
||||||
|
if (leadingWildcard) { |
||||||
|
sb.append(".*"); |
||||||
|
} |
||||||
|
sb.append(valueToUse); |
||||||
|
if (trailingWildcard) { |
||||||
|
sb.append(".*"); |
||||||
|
} |
||||||
|
|
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,449 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2015 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. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.mongodb.core.convert; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
import static org.springframework.data.domain.Example.*; |
||||||
|
import static org.springframework.data.domain.PropertySpecifier.*; |
||||||
|
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; |
||||||
|
import static org.springframework.data.mongodb.test.util.IsBsonObject.*; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import org.hamcrest.core.Is; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.domain.Example; |
||||||
|
import org.springframework.data.geo.Point; |
||||||
|
import org.springframework.data.mongodb.MongoDbFactory; |
||||||
|
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes; |
||||||
|
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.WithDBRef; |
||||||
|
import org.springframework.data.mongodb.core.mapping.DBRef; |
||||||
|
import org.springframework.data.mongodb.core.mapping.Document; |
||||||
|
import org.springframework.data.mongodb.core.mapping.Field; |
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; |
||||||
|
|
||||||
|
import com.mongodb.BasicDBObject; |
||||||
|
import com.mongodb.BasicDBObjectBuilder; |
||||||
|
import com.mongodb.DBObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Christoph Strobl |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class MongoExampleMapperUnitTests { |
||||||
|
|
||||||
|
MongoExampleMapper mapper; |
||||||
|
MongoMappingContext context; |
||||||
|
MappingMongoConverter converter; |
||||||
|
|
||||||
|
@Mock MongoDbFactory factory; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
|
||||||
|
this.context = new MongoMappingContext(); |
||||||
|
|
||||||
|
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); |
||||||
|
this.converter.afterPropertiesSet(); |
||||||
|
|
||||||
|
this.mapper = new MongoExampleMapper(converter); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.id = "steelheart"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("_id", "steelheart").get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.id = "steelheart"; |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, |
||||||
|
is(new BasicDBObjectBuilder().add("_id", "steelheart").add("stringValue", "firefight").add("intValue", 100) |
||||||
|
.get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "firefight").add("intValue", 100).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.listOfString = Arrays.asList("Prof", "Tia", "David"); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("listOfString", Arrays.asList("Prof", "Tia", "David")).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.customNamedField = "Mitosis"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "Mitosis").get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenienMatchMode() { |
||||||
|
|
||||||
|
WrapperDocument probe = new WrapperDocument(); |
||||||
|
probe.flatDoc = new FlatDocument(); |
||||||
|
probe.flatDoc.stringValue = "conflux"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WrapperDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "conflux").get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStriktMatchMode() { |
||||||
|
|
||||||
|
WrapperDocument probe = new WrapperDocument(); |
||||||
|
probe.flatDoc = new FlatDocument(); |
||||||
|
probe.flatDoc.stringValue = "conflux"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).includeNullValues().get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, isBsonObject().containing("flatDoc.stringValue", "conflux")); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).matchStringsStartingWith().get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, |
||||||
|
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "^firefight")) |
||||||
|
.add("intValue", 100).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).matchStringsEndingWith().get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, |
||||||
|
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight$")) |
||||||
|
.add("intValue", 100).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).matchStringsEndingWith().matchStringsWithIgnoreCase().get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat( |
||||||
|
dbo, |
||||||
|
is(new BasicDBObjectBuilder() |
||||||
|
.add("stringValue", new BasicDBObjectBuilder().add("$regex", "firefight$").add("$options", "i").get()) |
||||||
|
.add("intValue", 100).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.intValue = 100; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).matchStringsWithIgnoreCase().get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat( |
||||||
|
dbo, |
||||||
|
is(new BasicDBObjectBuilder() |
||||||
|
.add("stringValue", |
||||||
|
new BasicDBObjectBuilder().add("$regex", Pattern.quote("firefight")).add("$options", "i").get()) |
||||||
|
.add("intValue", 100).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedWhenContainingDBRef() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "steelheart"; |
||||||
|
probe.referenceDocument = new ReferenceDocument(); |
||||||
|
probe.referenceDocument.id = "200"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); |
||||||
|
com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class); |
||||||
|
|
||||||
|
assertThat(reference.getId(), Is.<Object> is("200")); |
||||||
|
assertThat(reference.getCollectionName(), is("refDoc")); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedWhenDBRefIsNull() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "steelheart"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "steelheart").get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { |
||||||
|
|
||||||
|
ClassWithGeoTypes probe = new ClassWithGeoTypes(); |
||||||
|
probe.legacyPoint = new Point(10D, 20D); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); |
||||||
|
|
||||||
|
assertThat(dbo.get("legacyPoint.x"), Is.<Object> is(10D)); |
||||||
|
assertThat(dbo.get("legacyPoint.y"), Is.<Object> is(20D)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldExcludeFieldWithCustomNameCorrectly() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.customNamedField = "foo"; |
||||||
|
probe.intValue = 10; |
||||||
|
probe.stringValue = "string"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).ignore("customNamedField").get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "string").add("intValue", 10).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldExcludeFieldCorrectly() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.customNamedField = "foo"; |
||||||
|
probe.intValue = 10; |
||||||
|
probe.stringValue = "string"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).ignore("stringValue").get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "foo").add("intValue", 10).get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldExcludeNestedFieldCorrectly() { |
||||||
|
|
||||||
|
WrapperDocument probe = new WrapperDocument(); |
||||||
|
probe.flatDoc = new FlatDocument(); |
||||||
|
probe.flatDoc.customNamedField = "foo"; |
||||||
|
probe.flatDoc.intValue = 10; |
||||||
|
probe.flatDoc.stringValue = "string"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).ignore("flatDoc.stringValue").get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.custom_field_name", "foo").add("flatDoc.intValue", 10) |
||||||
|
.get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { |
||||||
|
|
||||||
|
WrapperDocument probe = new WrapperDocument(); |
||||||
|
probe.flatDoc = new FlatDocument(); |
||||||
|
probe.flatDoc.customNamedField = "foo"; |
||||||
|
probe.flatDoc.intValue = 10; |
||||||
|
probe.flatDoc.stringValue = "string"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).ignore("flatDoc.customNamedField").get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "string").add("flatDoc.intValue", 10) |
||||||
|
.get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { |
||||||
|
|
||||||
|
FlatDocument probe = new FlatDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.customNamedField = "steelheart"; |
||||||
|
|
||||||
|
Example<?> example = newExampleOf(probe).specify(newPropertySpecifier("stringValue").matchStringContaining().get()) |
||||||
|
.get(); |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat( |
||||||
|
dbo, |
||||||
|
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", ".*firefight.*")) |
||||||
|
.add("custom_field_name", "steelheart").get())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void mappingShouldIncludePropertiesFromHierarchicalDocument() { |
||||||
|
|
||||||
|
HierachicalDocument probe = new HierachicalDocument(); |
||||||
|
probe.stringValue = "firefight"; |
||||||
|
probe.customNamedField = "steelheart"; |
||||||
|
probe.anotherStringValue = "calamity"; |
||||||
|
|
||||||
|
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||||
|
|
||||||
|
assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity")); |
||||||
|
} |
||||||
|
|
||||||
|
static class FlatDocument { |
||||||
|
|
||||||
|
@Id String id; |
||||||
|
String stringValue; |
||||||
|
@Field("custom_field_name") String customNamedField; |
||||||
|
Integer intValue; |
||||||
|
List<String> listOfString; |
||||||
|
@DBRef ReferenceDocument referenceDocument; |
||||||
|
} |
||||||
|
|
||||||
|
static class HierachicalDocument extends FlatDocument { |
||||||
|
|
||||||
|
String anotherStringValue; |
||||||
|
} |
||||||
|
|
||||||
|
static class WrapperDocument { |
||||||
|
|
||||||
|
@Id String id; |
||||||
|
FlatDocument flatDoc; |
||||||
|
} |
||||||
|
|
||||||
|
@Document(collection = "refDoc") |
||||||
|
static class ReferenceDocument { |
||||||
|
|
||||||
|
@Id String id; |
||||||
|
String value; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,174 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2015 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. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.mongodb.core.temp; |
||||||
|
|
||||||
|
import java.net.UnknownHostException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.hamcrest.core.Is; |
||||||
|
import org.junit.Assert; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.domain.Example; |
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate; |
||||||
|
import org.springframework.data.mongodb.core.mapping.Document; |
||||||
|
import org.springframework.data.mongodb.core.mapping.Field; |
||||||
|
import org.springframework.data.mongodb.core.query.Criteria; |
||||||
|
import org.springframework.data.mongodb.core.query.Query; |
||||||
|
|
||||||
|
import com.mongodb.MongoClient; |
||||||
|
|
||||||
|
public class QueryByExampleTests { |
||||||
|
|
||||||
|
MongoTemplate template; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() throws UnknownHostException { |
||||||
|
|
||||||
|
template = new MongoTemplate(new MongoClient(), "query-by-example"); |
||||||
|
template.remove(new Query(), Person.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleShouldWorkForSimpleProperty() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
sample.lastname = "stark"; |
||||||
|
|
||||||
|
List<Person> result = template.findByExample(sample); |
||||||
|
Assert.assertThat(result.size(), Is.is(2)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleShouldWorkForMultipleProperties() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
sample.lastname = "stark"; |
||||||
|
sample.firstname = "arya"; |
||||||
|
|
||||||
|
List<Person> result = template.findByExample(sample); |
||||||
|
Assert.assertThat(result.size(), Is.is(1)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleShouldWorkForIdProperty() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person p4 = new Person(); |
||||||
|
template.save(p4); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
sample.id = p4.id; |
||||||
|
|
||||||
|
List<Person> result = template.findByExample(sample); |
||||||
|
Assert.assertThat(result.size(), Is.is(1)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleShouldReturnEmptyListIfNotMatching() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
sample.firstname = "jon"; |
||||||
|
sample.firstname = "stark"; |
||||||
|
|
||||||
|
List<Person> result = template.findByExample(sample); |
||||||
|
Assert.assertThat(result.size(), Is.is(0)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
|
||||||
|
List<Person> result = template.findByExample(sample); |
||||||
|
Assert.assertThat(result.size(), Is.is(3)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see DATAMONGO-1245 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void findByExampleWithCriteria() { |
||||||
|
|
||||||
|
init(); |
||||||
|
|
||||||
|
Person sample = new Person(); |
||||||
|
sample.lastname = "stark"; |
||||||
|
|
||||||
|
Query query = new Query(new Criteria().alike(new Example<Person>(sample)).and("firstname").regex("^ary*")); |
||||||
|
|
||||||
|
List<Person> result = template.find(query, Person.class); |
||||||
|
Assert.assertThat(result.size(), Is.is(1)); |
||||||
|
} |
||||||
|
|
||||||
|
public void init() { |
||||||
|
|
||||||
|
Person p1 = new Person(); |
||||||
|
p1.firstname = "bran"; |
||||||
|
p1.lastname = "stark"; |
||||||
|
|
||||||
|
Person p2 = new Person(); |
||||||
|
p2.firstname = "jon"; |
||||||
|
p2.lastname = "snow"; |
||||||
|
|
||||||
|
Person p3 = new Person(); |
||||||
|
p3.firstname = "arya"; |
||||||
|
p3.lastname = "stark"; |
||||||
|
|
||||||
|
template.save(p1); |
||||||
|
template.save(p2); |
||||||
|
template.save(p3); |
||||||
|
} |
||||||
|
|
||||||
|
@Document(collection = "dramatis-personae") |
||||||
|
static class Person { |
||||||
|
|
||||||
|
@Id String id; |
||||||
|
String firstname; |
||||||
|
|
||||||
|
@Field("last_name") String lastname; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "Person [id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + "]"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue