DATAMONGO-973 - Add support for deriving full-text queries.
Added support to execute full-text queries on repositories. Query methods now can have a parameter of type TextCriteria which will be triggering a text search clause for the property annotated with @TestScore.
Retrieving document score and sorting by score is only possible if the entity holds a property annotated with @TextScore. If present, any find execution will be enriched so that it asserts loading of the according { $meta : textScore } field. The sort object will only be mapped in case the existing sort property already exists - in that case we replace the existing expression for the property with its $meta representation.
This allows for example the following:
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("term");
repository.findAllBy(criteria, new Sort("score"));
repository.findAllBy(criteria, new PageRequest(0, 10, Direction.DESC, "score"));
repository.findByFooOrderByScoreDesc("foo", criteria);
For more details and examples see the "Full text search queries" section in the reference manual.
This commit is contained in:
committed by
Oliver Gierke
parent
168cf3e1f6
commit
151b1d4510
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<context version="7.1.9.205">
|
||||
<context version="7.1.10.209">
|
||||
<scope type="Project" name="spring-data-mongodb">
|
||||
<element type="TypeFilterReferenceOverridden" name="Filter">
|
||||
<element type="IncludeTypePattern" name="org.springframework.data.mongodb.**"/>
|
||||
@@ -32,6 +32,7 @@
|
||||
<element type="IncludeTypePattern" name="**.config.**"/>
|
||||
</element>
|
||||
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
|
||||
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API" type="AllowedDependency"/>
|
||||
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Implementation" type="AllowedDependency"/>
|
||||
</element>
|
||||
<dependency toName="Project|spring-data-mongodb::Layer|Config" type="AllowedDependency"/>
|
||||
|
||||
@@ -1616,7 +1616,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
CursorPreparer preparer, DbObjectCallback<T> objectCallback) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
|
||||
|
||||
DBObject mappedFields = queryMapper.getMappedFields(fields, entity);
|
||||
DBObject mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -1969,8 +1970,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
return null;
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
||||
return queryMapper.getMappedObject(query.getSortObject(), entity);
|
||||
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
|
||||
}
|
||||
|
||||
// Callback implementations
|
||||
@@ -2030,7 +2030,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
|
||||
public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException {
|
||||
if (fields == null) {
|
||||
if (fields == null || fields.toMap().isEmpty()) {
|
||||
return collection.find(query);
|
||||
} else {
|
||||
return collection.find(query, fields);
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToS
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToURLConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.URLToStringConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -58,6 +59,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class CustomConversions {
|
||||
|
||||
@@ -106,7 +108,8 @@ public class CustomConversions {
|
||||
toRegister.add(URLToStringConverter.INSTANCE);
|
||||
toRegister.add(StringToURLConverter.INSTANCE);
|
||||
toRegister.add(DBObjectToStringConverter.INSTANCE);
|
||||
|
||||
toRegister.add(TermToStringConverter.INSTANCE);
|
||||
|
||||
toRegister.addAll(JodaTimeConverters.getConvertersToRegister());
|
||||
toRegister.addAll(GeoConverters.getConvertersToRegister());
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mongodb.core.query.Term;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
@@ -34,6 +36,7 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
abstract class MongoConverters {
|
||||
|
||||
@@ -160,4 +163,19 @@ abstract class MongoConverters {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
@WritingConverter
|
||||
public static enum TermToStringConverter implements Converter<Term, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Term source) {
|
||||
return source == null ? null : source.getFormatted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ import com.mongodb.DBRef;
|
||||
public class QueryMapper {
|
||||
|
||||
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
|
||||
private static final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
|
||||
|
||||
private enum MetaMapping {
|
||||
FORCE, WHEN_PRESENT, IGNORE;
|
||||
}
|
||||
|
||||
private final ConversionService conversionService;
|
||||
private final MongoConverter converter;
|
||||
@@ -119,6 +124,61 @@ public class QueryMapper {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps fields used for sorting to the {@link MongoPersistentEntity}s properties. <br />
|
||||
* Also converts properties to their {@code $meta} representation if present.
|
||||
*
|
||||
* @param sortObject
|
||||
* @param entity
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public DBObject getMappedSort(DBObject sortObject, MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (sortObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DBObject mappedSort = getMappedObject(sortObject, entity);
|
||||
mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT);
|
||||
return mappedSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps fields to retrieve to the {@link MongoPersistentEntity}s properties. <br />
|
||||
* Also onverts and potentially adds missing property {@code $meta} representation.
|
||||
*
|
||||
* @param fieldsObject
|
||||
* @param entity
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public DBObject getMappedFields(DBObject fieldsObject, MongoPersistentEntity<?> entity) {
|
||||
|
||||
DBObject mappedFields = fieldsObject != null ? getMappedObject(fieldsObject, entity) : new BasicDBObject();
|
||||
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
|
||||
return mappedFields.keySet().isEmpty() ? null : mappedFields;
|
||||
}
|
||||
|
||||
private void mapMetaAttributes(DBObject source, MongoPersistentEntity<?> entity, MetaMapping metaMapping) {
|
||||
|
||||
if (entity == null || source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.hasTextScoreProperty() && !MetaMapping.IGNORE.equals(metaMapping)) {
|
||||
MongoPersistentProperty textScoreProperty = entity.getTextScoreProperty();
|
||||
if (MetaMapping.FORCE.equals(metaMapping)
|
||||
|| (MetaMapping.WHEN_PRESENT.equals(metaMapping) && source.containsField(textScoreProperty.getFieldName()))) {
|
||||
source.putAll(getMappedTextScoreField(textScoreProperty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DBObject getMappedTextScoreField(MongoPersistentProperty property) {
|
||||
return new BasicDBObject(property.getFieldName(), META_TEXT_SCORE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account
|
||||
*
|
||||
|
||||
@@ -276,7 +276,7 @@ public class TextIndexDefinition implements IndexDefinition {
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder onField(String fieldname) {
|
||||
return onField(fieldname, Float.NaN);
|
||||
return onField(fieldname, 1F);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,24 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
return this.language;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getTextScoreProperty()
|
||||
*/
|
||||
@Override
|
||||
public MongoPersistentProperty getTextScoreProperty() {
|
||||
return getPersistentProperty(TextScore.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#hasTextScoreProperty()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasTextScoreProperty() {
|
||||
return getTextScoreProperty() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.BasicPersistentEntity#verify()
|
||||
|
||||
@@ -40,4 +40,21 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
|
||||
*/
|
||||
String getLanguage();
|
||||
|
||||
/**
|
||||
* Returns the property holding text score value.
|
||||
*
|
||||
* @since 1.6
|
||||
* @see #hasTextScoreProperty()
|
||||
* @return {@literal null} if not present.
|
||||
*/
|
||||
MongoPersistentProperty getTextScoreProperty();
|
||||
|
||||
/**
|
||||
* Returns whether the entity has a {@link TextScore} property.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return true if property annotated with {@link TextScore} is present.
|
||||
*/
|
||||
boolean hasTextScoreProperty();
|
||||
|
||||
}
|
||||
|
||||
@@ -50,8 +50,12 @@ public class BasicQuery extends Query {
|
||||
this.fieldsObject = fieldsObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.Query#addCriteria(org.springframework.data.mongodb.core.query.CriteriaDefinition)
|
||||
*/
|
||||
@Override
|
||||
public Query addCriteria(Criteria criteria) {
|
||||
public Query addCriteria(CriteriaDefinition criteria) {
|
||||
this.queryObject.putAll(criteria.getCriteriaObject());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2011 the original author or authors.
|
||||
* Copyright 2010-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.
|
||||
@@ -17,8 +17,25 @@ package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface CriteriaDefinition {
|
||||
|
||||
/**
|
||||
* Get {@link DBObject} representation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
DBObject getCriteriaObject();
|
||||
|
||||
}
|
||||
/**
|
||||
* Get the identifying {@literal key}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
}
|
||||
|
||||
@@ -343,7 +343,6 @@ public final class NearQuery {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public DBObject toDBObject() {
|
||||
|
||||
BasicDBObject dbObject = new BasicDBObject();
|
||||
|
||||
@@ -39,13 +39,14 @@ import com.mongodb.DBObject;
|
||||
* @author Thomas Risberg
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class Query {
|
||||
|
||||
private static final String RESTRICTED_TYPES_KEY = "_$RESTRICTED_TYPES";
|
||||
|
||||
private final Set<Class<?>> restrictedTypes = new HashSet<Class<?>>();
|
||||
private final Map<String, Criteria> criteria = new LinkedHashMap<String, Criteria>();
|
||||
private final Map<String, CriteriaDefinition> criteria = new LinkedHashMap<String, CriteriaDefinition>();
|
||||
private Field fieldSpec;
|
||||
private Sort sort;
|
||||
private int skip;
|
||||
@@ -53,42 +54,48 @@ public class Query {
|
||||
private String hint;
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link Query} using the provided {@link Criteria}.
|
||||
* Static factory method to create a {@link Query} using the provided {@link CriteriaDefinition}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @param criteriaDefinition must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public static Query query(Criteria criteria) {
|
||||
return new Query(criteria);
|
||||
public static Query query(CriteriaDefinition criteriaDefinition) {
|
||||
return new Query(criteriaDefinition);
|
||||
}
|
||||
|
||||
public Query() {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Query} using the given {@link Criteria}.
|
||||
* Creates a new {@link Query} using the given {@link CriteriaDefinition}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @param criteriaDefinition must not be {@literal null}.
|
||||
* @since 1.6
|
||||
*/
|
||||
public Query(Criteria criteria) {
|
||||
addCriteria(criteria);
|
||||
public Query(CriteriaDefinition criteriaDefinition) {
|
||||
addCriteria(criteriaDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Criteria} to the current {@link Query}.
|
||||
* Adds the given {@link CriteriaDefinition} to the current {@link Query}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @param criteriaDefinition must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public Query addCriteria(Criteria criteria) {
|
||||
CriteriaDefinition existing = this.criteria.get(criteria.getKey());
|
||||
String key = criteria.getKey();
|
||||
public Query addCriteria(CriteriaDefinition criteriaDefinition) {
|
||||
|
||||
CriteriaDefinition existing = this.criteria.get(criteriaDefinition.getKey());
|
||||
String key = criteriaDefinition.getKey();
|
||||
|
||||
if (existing == null) {
|
||||
this.criteria.put(key, criteria);
|
||||
this.criteria.put(key, criteriaDefinition);
|
||||
} else {
|
||||
throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, "
|
||||
+ "you can't add a second '" + key + "' criteria. " + "Query already contains '"
|
||||
+ existing.getCriteriaObject() + "'.");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -268,8 +275,8 @@ public class Query {
|
||||
return hint;
|
||||
}
|
||||
|
||||
protected List<Criteria> getCriteria() {
|
||||
return new ArrayList<Criteria>(this.criteria.values());
|
||||
protected List<CriteriaDefinition> getCriteria() {
|
||||
return new ArrayList<CriteriaDefinition>(this.criteria.values());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
/**
|
||||
* A {@link Term} defines one or multiple words {@link Type#WORD} or phrases {@link Type#PHRASE} to be used in the
|
||||
@@ -24,7 +24,7 @@ package org.springframework.data.mongodb.core.query.text;
|
||||
*/
|
||||
public class Term {
|
||||
|
||||
enum Type {
|
||||
public enum Type {
|
||||
WORD, PHRASE;
|
||||
}
|
||||
|
||||
@@ -13,16 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -30,42 +26,59 @@ import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CriteriaDefinition} to be used for full text search .
|
||||
* Implementation of {@link CriteriaDefinition} to be used for full text search.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
public class TextCriteria extends Criteria {
|
||||
public class TextCriteria implements CriteriaDefinition {
|
||||
|
||||
private final List<Term> terms;
|
||||
private String language;
|
||||
private List<Term> terms;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TextCriteria}.
|
||||
*
|
||||
* @see #forDefaultLanguage()
|
||||
* @see #forLanguage(String)
|
||||
*/
|
||||
public TextCriteria() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private TextCriteria(String language) {
|
||||
|
||||
this.language = language;
|
||||
this.terms = new ArrayList<Term>();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject()
|
||||
/**
|
||||
* Returns a new {@link TextCriteria} for the default language.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public DBObject getCriteriaObject() {
|
||||
|
||||
BasicDBObjectBuilder builder = new BasicDBObjectBuilder();
|
||||
|
||||
if (StringUtils.hasText(language)) {
|
||||
builder.add("$language", language);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(terms)) {
|
||||
builder.add("$search", join(terms.iterator()));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$text", builder.get());
|
||||
public static TextCriteria forDefaultLanguage() {
|
||||
return new TextCriteria();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param words
|
||||
* For a full list of supported languages see the mongdodb reference manual for <a
|
||||
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
|
||||
*
|
||||
* @param language
|
||||
* @return
|
||||
*/
|
||||
public static TextCriteria forLanguage(String language) {
|
||||
|
||||
Assert.hasText(language, "Language must not be null or empty!");
|
||||
return new TextCriteria(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link TextCriteria} to match any of the given words.
|
||||
*
|
||||
* @param words the words to match.
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria matchingAny(String... words) {
|
||||
@@ -73,22 +86,21 @@ public class TextCriteria extends Criteria {
|
||||
for (String word : words) {
|
||||
matching(word);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given {@link Term} to criteria.
|
||||
* Adds given {@link Term} to criteria.
|
||||
*
|
||||
* @param term must not be null.
|
||||
* @param term must not be {@literal null}.
|
||||
*/
|
||||
public void matching(Term term) {
|
||||
public TextCriteria matching(Term term) {
|
||||
|
||||
Assert.notNull(term, "Term to add must not be null.");
|
||||
this.terms.add(term);
|
||||
}
|
||||
|
||||
private void notMatching(Term term) {
|
||||
matching(term.negate());
|
||||
this.terms.add(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +122,7 @@ public class TextCriteria extends Criteria {
|
||||
public TextCriteria notMatching(String term) {
|
||||
|
||||
if (StringUtils.hasText(term)) {
|
||||
notMatching(new Term(term, Term.Type.WORD));
|
||||
matching(new Term(term, Term.Type.WORD).negate());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -136,7 +148,7 @@ public class TextCriteria extends Criteria {
|
||||
public TextCriteria notMatchingPhrase(String phrase) {
|
||||
|
||||
if (StringUtils.hasText(phrase)) {
|
||||
notMatching(new Term(phrase, Term.Type.PHRASE));
|
||||
matching(new Term(phrase, Term.Type.PHRASE).negate());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -155,69 +167,45 @@ public class TextCriteria extends Criteria {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getKey()
|
||||
*/
|
||||
public static TextCriteria forDefaultLanguage() {
|
||||
return new TextCriteriaBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a full list of supported languages see the mongdodb reference manual for <a
|
||||
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
|
||||
*
|
||||
* @param language
|
||||
* @return
|
||||
*/
|
||||
public static TextCriteria forLanguage(String language) {
|
||||
return new TextCriteriaBuilder().withLanguage(language).build();
|
||||
}
|
||||
|
||||
private static String join(Iterator<Term> iterator) {
|
||||
|
||||
Term first = iterator.next();
|
||||
if (!iterator.hasNext()) {
|
||||
return first.getFormatted();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
if (first != null) {
|
||||
buf.append(first);
|
||||
}
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
buf.append(' ');
|
||||
Term obj = iterator.next();
|
||||
if (obj != null) {
|
||||
buf.append(obj.getFormatted());
|
||||
}
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static class TextCriteriaBuilder {
|
||||
|
||||
private TextCriteria instance;
|
||||
|
||||
public TextCriteriaBuilder() {
|
||||
this.instance = new TextCriteria();
|
||||
}
|
||||
|
||||
public TextCriteriaBuilder withLanguage(String language) {
|
||||
this.instance.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextCriteria build() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "$text";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getCriteriaObject() {
|
||||
|
||||
BasicDBObjectBuilder builder = new BasicDBObjectBuilder();
|
||||
|
||||
if (StringUtils.hasText(language)) {
|
||||
builder.add("$language", language);
|
||||
}
|
||||
|
||||
if (!terms.isEmpty()) {
|
||||
builder.add("$search", join(terms));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$text", builder.get());
|
||||
}
|
||||
|
||||
private String join(Iterable<Term> terms) {
|
||||
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
for (Term term : terms) {
|
||||
if (term != null) {
|
||||
result.add(term.getFormatted());
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(result, " ");
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
@@ -147,6 +145,10 @@ public class TextQuery extends Query {
|
||||
return scoreFieldName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.Query#getFieldsObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getFieldsObject() {
|
||||
|
||||
@@ -155,24 +157,32 @@ public class TextQuery extends Query {
|
||||
}
|
||||
|
||||
DBObject fields = super.getFieldsObject();
|
||||
|
||||
if (fields == null) {
|
||||
fields = new BasicDBObject();
|
||||
}
|
||||
|
||||
fields.put(getScoreFieldName(), META_TEXT_SCORE);
|
||||
return fields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.Query#getSortObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getSortObject() {
|
||||
|
||||
DBObject sort = new BasicDBObject();
|
||||
|
||||
if (this.sortByScore) {
|
||||
sort.put(getScoreFieldName(), META_TEXT_SCORE);
|
||||
}
|
||||
|
||||
if (super.getSortObject() != null) {
|
||||
sort.putAll(super.getSortObject());
|
||||
}
|
||||
|
||||
return sort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2012 the original author or authors.
|
||||
* Copyright 2010-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.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
* Mongo specific {@link org.springframework.data.repository.Repository} interface.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface MongoRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 the original author or authors.
|
||||
* Copyright 2011-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.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -38,6 +39,7 @@ import com.mongodb.DBRef;
|
||||
* Custom {@link ParameterAccessor} that uses a {@link MongoWriter} to serialize parameters into Mongo format.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class ConvertingParameterAccessor implements MongoParameterAccessor {
|
||||
|
||||
@@ -110,6 +112,14 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
|
||||
return delegate.getGeoNearLocation();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText()
|
||||
*/
|
||||
public TextCriteria getFullText() {
|
||||
return delegate.getFullText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value with the underlying {@link MongoWriter}.
|
||||
*
|
||||
|
||||
@@ -17,12 +17,14 @@ package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
/**
|
||||
* Mongo-specific {@link ParameterAccessor} exposing a maximum distance parameter.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoParameterAccessor extends ParameterAccessor {
|
||||
|
||||
@@ -40,4 +42,12 @@ public interface MongoParameterAccessor extends ParameterAccessor {
|
||||
* @return
|
||||
*/
|
||||
Point getGeoNearLocation();
|
||||
|
||||
/**
|
||||
* Returns the {@link TextCriteria} to be used for full text query.
|
||||
*
|
||||
* @return null if not set.
|
||||
* @since 1.6
|
||||
*/
|
||||
TextCriteria getFullText();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 the original author or authors.
|
||||
* Copyright 2011-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,6 +22,7 @@ import java.util.List;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.repository.Near;
|
||||
import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
@@ -31,10 +32,13 @@ import org.springframework.data.repository.query.Parameters;
|
||||
* Custom extension of {@link Parameters} discovering additional
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoParameters extends Parameters<MongoParameters, MongoParameter> {
|
||||
|
||||
private final Integer distanceIndex;
|
||||
private final Integer fullTextIndex;
|
||||
|
||||
private Integer nearIndex;
|
||||
|
||||
/**
|
||||
@@ -48,6 +52,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
|
||||
super(method);
|
||||
List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes());
|
||||
this.distanceIndex = parameterTypes.indexOf(Distance.class);
|
||||
this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class);
|
||||
|
||||
if (this.nearIndex == null && isGeoNearMethod) {
|
||||
this.nearIndex = getNearIndex(parameterTypes);
|
||||
@@ -56,12 +61,14 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
|
||||
}
|
||||
}
|
||||
|
||||
private MongoParameters(List<MongoParameter> parameters, Integer distanceIndex, Integer nearIndex) {
|
||||
private MongoParameters(List<MongoParameter> parameters, Integer distanceIndex, Integer nearIndex,
|
||||
Integer fullTextIndex) {
|
||||
|
||||
super(parameters);
|
||||
|
||||
this.distanceIndex = distanceIndex;
|
||||
this.nearIndex = nearIndex;
|
||||
this.fullTextIndex = fullTextIndex;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||
@@ -124,13 +131,31 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
|
||||
return nearIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ths inde of the parameter to be used as a textquery param
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public int getFullTextParameterIndex() {
|
||||
return fullTextIndex != null ? fullTextIndex.intValue() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public boolean hasFullTextParameter() {
|
||||
return this.fullTextIndex != null && this.fullTextIndex.intValue() >= 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected MongoParameters createFrom(List<MongoParameter> parameters) {
|
||||
return new MongoParameters(parameters, this.distanceIndex, this.nearIndex);
|
||||
return new MongoParameters(parameters, this.distanceIndex, this.nearIndex, this.fullTextIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +187,8 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
|
||||
*/
|
||||
@Override
|
||||
public boolean isSpecialParameter() {
|
||||
return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter();
|
||||
return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter()
|
||||
|| TextCriteria.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
private boolean isNearParameter() {
|
||||
@@ -181,5 +207,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
|
||||
private boolean hasNearAnnotation() {
|
||||
return parameter.getParameterAnnotation(Near.class) != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@ package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.query.Term;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Mongo-specific {@link ParametersParameterAccessor} to allow access to the {@link Distance} parameter.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoParametersParameterAccessor extends ParametersParameterAccessor implements MongoParameterAccessor {
|
||||
|
||||
@@ -77,4 +82,35 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
|
||||
|
||||
return (Point) value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText()
|
||||
*/
|
||||
@Override
|
||||
public TextCriteria getFullText() {
|
||||
int index = method.getParameters().getFullTextParameterIndex();
|
||||
return index >= 0 ? potentiallyConvertFullText(getValue(index)) : null;
|
||||
}
|
||||
|
||||
protected TextCriteria potentiallyConvertFullText(Object fullText) {
|
||||
|
||||
Assert.notNull(fullText, "Fulltext parameter must not be 'null'.");
|
||||
|
||||
if (fullText instanceof String) {
|
||||
return TextCriteria.forDefaultLanguage().matching((String) fullText);
|
||||
}
|
||||
|
||||
if (fullText instanceof Term) {
|
||||
return TextCriteria.forDefaultLanguage().matching((Term) fullText);
|
||||
}
|
||||
|
||||
if (fullText instanceof TextCriteria) {
|
||||
return ((TextCriteria) fullText);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Expected full text parameter to be one of String, Term or TextCriteria but found %s.",
|
||||
ClassUtils.getShortName(fullText.getClass())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +146,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
@Override
|
||||
protected Query complete(Criteria criteria, Sort sort) {
|
||||
|
||||
if (criteria == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Query query = new Query(criteria).with(sort);
|
||||
Query query = (criteria == null ? new Query() : new Query(criteria)).with(sort);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Created query " + query);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 the original author or authors.
|
||||
* Copyright 2011-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.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
* Mongo specific implementation of {@link QueryMethod}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
@@ -143,7 +144,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether te query is a geo near query.
|
||||
* Returns whether the query is a geo near query.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
@@ -77,6 +78,11 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
query.limit(tree.getMaxResults());
|
||||
}
|
||||
|
||||
TextCriteria textCriteria = accessor.getFullText();
|
||||
if (textCriteria != null) {
|
||||
query.addCriteria(textCriteria);
|
||||
}
|
||||
|
||||
String fieldSpec = this.getQueryMethod().getFieldSpecification();
|
||||
|
||||
if (!StringUtils.hasText(fieldSpec)) {
|
||||
@@ -87,6 +93,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
|
||||
BasicQuery result = new BasicQuery(query.getQueryObject().toString(), fieldSpec);
|
||||
result.setSortObject(query.getSortObject());
|
||||
|
||||
return result;
|
||||
|
||||
} catch (JSONParseException o_O) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2012 the original author or authors.
|
||||
* Copyright 2010-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.
|
||||
@@ -40,6 +40,7 @@ import org.springframework.util.Assert;
|
||||
* Repository base implementation for Mongo.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SimpleMongoRepository<T, ID extends Serializable> implements MongoRepository<T, ID> {
|
||||
|
||||
@@ -232,4 +233,5 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements MongoR
|
||||
protected MongoEntityInformation<T, ID> getEntityInformation() {
|
||||
return entityInformation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ 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 org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -600,6 +601,63 @@ public class QueryMapperUnitTests {
|
||||
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent() {
|
||||
|
||||
Query query = new Query();
|
||||
|
||||
DBObject dbo = mapper.getMappedFields(query.getFieldsObject(),
|
||||
context.getPersistentEntity(WithTextScoreProperty.class));
|
||||
|
||||
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() {
|
||||
|
||||
Query query = new Query();
|
||||
query.fields().include("textScore");
|
||||
|
||||
DBObject dbo = mapper.getMappedFields(query.getFieldsObject(),
|
||||
context.getPersistentEntity(WithTextScoreProperty.class));
|
||||
|
||||
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() {
|
||||
|
||||
Query query = new Query().with(new Sort("textScore"));
|
||||
|
||||
DBObject dbo = mapper
|
||||
.getMappedSort(query.getSortObject(), context.getPersistentEntity(WithTextScoreProperty.class));
|
||||
|
||||
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() {
|
||||
|
||||
Query query = new Query().with(new Sort("id"));
|
||||
|
||||
DBObject dbo = mapper
|
||||
.getMappedSort(query.getSortObject(), context.getPersistentEntity(WithTextScoreProperty.class));
|
||||
|
||||
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("_id", 1).get()));
|
||||
}
|
||||
|
||||
@Document
|
||||
public class Foo {
|
||||
@Id private ObjectId id;
|
||||
@@ -675,4 +733,10 @@ public class QueryMapperUnitTests {
|
||||
|
||||
@DBRef Map<String, Sample> mapWithDBRef;
|
||||
}
|
||||
|
||||
class WithTextScoreProperty {
|
||||
|
||||
@Id String id;
|
||||
@TextScore @Field("score") Float textScore;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.core.IsNull.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter;
|
||||
import org.springframework.data.mongodb.core.query.Term;
|
||||
import org.springframework.data.mongodb.core.query.Term.Type;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TermToStringConverterUnitTests {
|
||||
|
||||
/**
|
||||
* @DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotConvertNull() {
|
||||
assertThat(TermToStringConverter.INSTANCE.convert(null), nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldUseFormattedRepresentationForConversion() {
|
||||
|
||||
Term term = spy(new Term("foo", Type.WORD));
|
||||
TermToStringConverter.INSTANCE.convert(term);
|
||||
verify(term, times(1)).getFormatted();
|
||||
}
|
||||
}
|
||||
@@ -214,4 +214,14 @@ public class MongoPersistentEntityTestDummy<T> implements MongoPersistentEntity<
|
||||
public String getLanguage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoPersistentProperty getTextScoreProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTextScoreProperty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,12 @@ public class IsQuery<T extends Query> extends TypeSafeMatcher<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> where(Criteria criteria) {
|
||||
|
||||
this.query.putAll(criteria.getCriteriaObject());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
|
||||
@@ -117,8 +123,10 @@ public class IsQuery<T extends Query> extends TypeSafeMatcher<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<DBObject>(sort).matches(item.getSortObject())) {
|
||||
return false;
|
||||
if (item.getSortObject() == null && !sort.toMap().isEmpty()) {
|
||||
if (!new IsEqual<DBObject>(sort).matches(item.getSortObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!new IsEqual<DBObject>(fields).matches(item.getFieldsObject())) {
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.springframework.data.mongodb.core.query.IsQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.TextQuery;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -28,7 +30,7 @@ import com.mongodb.DBObject;
|
||||
* @author Christoph Strobl
|
||||
* @param <T>
|
||||
*/
|
||||
public class IsTextQuery<T extends TextQuery> extends IsQuery<T> {
|
||||
public class IsTextQuery<T extends Query> extends IsQuery<T> {
|
||||
|
||||
private final String SCORE_DEFAULT_FIELDNAME = "score";
|
||||
private final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
|
||||
@@ -39,7 +41,7 @@ public class IsTextQuery<T extends TextQuery> extends IsQuery<T> {
|
||||
super();
|
||||
}
|
||||
|
||||
public static <T extends TextQuery> IsTextQuery<T> isTextQuery() {
|
||||
public static <T extends Query> IsTextQuery<T> isTextQuery() {
|
||||
return new IsTextQuery<T>();
|
||||
}
|
||||
|
||||
@@ -77,6 +79,41 @@ public class IsTextQuery<T extends TextQuery> extends IsQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsTextQuery<T> where(Criteria criteria) {
|
||||
|
||||
super.where(criteria);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsTextQuery<T> excludingField(String fieldname) {
|
||||
|
||||
super.excludingField(fieldname);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsTextQuery<T> includingField(String fieldname) {
|
||||
|
||||
super.includingField(fieldname);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsTextQuery<T> limitingTo(int limit) {
|
||||
|
||||
super.limitingTo(limit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsQuery<T> skippig(int skip) {
|
||||
|
||||
super.skippig(skip);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void appendLanguage(String language) {
|
||||
|
||||
DBObject dbo = getOrCreateTextDbo();
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Assert;
|
||||
@@ -25,6 +25,8 @@ import com.mongodb.DBObject;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TextCriteria}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextCriteriaUnitTests {
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.collection.IsEmptyCollection.*;
|
||||
@@ -40,7 +40,9 @@ import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.Language;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.mongodb.core.query.text.TextQueryTests.FullTextDoc.FullTextDocBuilder;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.core.query.TextQuery;
|
||||
import org.springframework.data.mongodb.core.query.TextQueryTests.FullTextDoc.FullTextDocBuilder;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
@@ -13,17 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query.text;
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.query.text.IsTextQuery.*;
|
||||
import static org.springframework.data.mongodb.core.query.IsTextQuery.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TextQuery}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextQueryUnitTests {
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.repository;
|
||||
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.hamcrest.core.IsCollectionContaining.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexed;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoClient;
|
||||
|
||||
/**
|
||||
* Integration tests for text searches on repository.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class MongoRepositoryTextSearchIntegrationTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule versionRule = MongoVersionRule.atLeast(new Version(2, 6, 0));
|
||||
|
||||
private static final FullTextDocument PASSENGER_57 = new FullTextDocument("1", "Passenger 57",
|
||||
"Passenger 57 is an action film that stars Wesley Snipes and Bruce Payne.");
|
||||
private static final FullTextDocument DEMOLITION_MAN = new FullTextDocument("2", "Demolition Man",
|
||||
"Demolition Man is a science fiction action comedy film staring Wesley Snipes and Sylvester Stallone.");
|
||||
private static final FullTextDocument DROP_ZONE = new FullTextDocument("3", "Drop Zone",
|
||||
"Drop Zone is an action film featuring Wesley Snipes and Gary Busey.");
|
||||
|
||||
@Autowired MongoTemplate template;
|
||||
FullTextRepository repo;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
template.indexOps(FullTextDocument.class).ensureIndex(
|
||||
new TextIndexDefinitionBuilder().onField("title").onField("content").build());
|
||||
this.repo = new MongoRepositoryFactory(this.template).getRepository(FullTextRepository.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
template.dropCollection(FullTextDocument.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void findAllByTextCriteriaShouldReturnMatchingDocuments() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
|
||||
List<FullTextDocument> result = repo.findAllBy(TextCriteria.forDefaultLanguage().matchingAny("stallone", "payne"));
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(PASSENGER_57, DEMOLITION_MAN));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void derivedFinderWithTextCriteriaReturnsCorrectResult() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
FullTextDocument blade = new FullTextDocument(
|
||||
"4",
|
||||
"Blade",
|
||||
"Blade is a 1998 American vampire-superhero-vigilante action film starring Wesley Snipes and Stephen Dorff, loosely based on the Marvel Comics character Blade");
|
||||
blade.nonTextIndexProperty = "foo";
|
||||
repo.save(blade);
|
||||
|
||||
List<FullTextDocument> result = repo.findByNonTextIndexProperty("foo",
|
||||
TextCriteria.forDefaultLanguage().matching("snipes"));
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result, hasItems(blade));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
|
||||
Page<FullTextDocument> page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("film"), new PageRequest(1,
|
||||
1, Direction.ASC, "id"));
|
||||
|
||||
assertThat(page.hasNext(), is(true));
|
||||
assertThat(page.hasPrevious(), is(true));
|
||||
assertThat(page.getTotalElements(), is(3L));
|
||||
assertThat(page.getContent().get(0), equalTo(DEMOLITION_MAN));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void findAllByTextCriteriaWithSortWorksCorrectly() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer.");
|
||||
repo.save(snipes);
|
||||
|
||||
List<FullTextDocument> result = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new Sort(
|
||||
"score"));
|
||||
|
||||
assertThat(result.size(), is(4));
|
||||
assertThat(result.get(0), equalTo(snipes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer.");
|
||||
repo.save(snipes);
|
||||
|
||||
Page<FullTextDocument> page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new PageRequest(
|
||||
0, 10, Direction.ASC, "score"));
|
||||
|
||||
assertThat(page.getTotalElements(), is(4L));
|
||||
assertThat(page.getContent().get(0), equalTo(snipes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherProperty() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer.");
|
||||
repo.save(snipes);
|
||||
|
||||
Page<FullTextDocument> page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new PageRequest(
|
||||
0, 10, Direction.ASC, "id"));
|
||||
|
||||
assertThat(page.getTotalElements(), is(4L));
|
||||
assertThat(page.getContent().get(0), equalTo(PASSENGER_57));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void derivedSortForTextScorePropertyWorksCorrectly() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer.");
|
||||
repo.save(snipes);
|
||||
|
||||
List<FullTextDocument> result = repo.findByNonTextIndexPropertyIsNullOrderByScoreDesc(TextCriteria
|
||||
.forDefaultLanguage().matching("snipes"));
|
||||
assertThat(result.get(0), equalTo(snipes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void derivedFinderMethodWithoutFullTextShouldNoCauseTroubleWhenHavingEntityWithTextScoreProperty() {
|
||||
|
||||
initRepoWithDefaultDocuments();
|
||||
List<FullTextDocument> result = repo.findByTitle(DROP_ZONE.getTitle());
|
||||
assertThat(result.get(0), equalTo(DROP_ZONE));
|
||||
assertThat(result.get(0).score, equalTo(0.0F));
|
||||
}
|
||||
|
||||
private void initRepoWithDefaultDocuments() {
|
||||
repo.save(Arrays.asList(PASSENGER_57, DEMOLITION_MAN, DROP_ZONE));
|
||||
}
|
||||
|
||||
@org.springframework.context.annotation.Configuration
|
||||
public static class Configuration extends AbstractMongoConfiguration {
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return ClassUtils.getShortNameAsProperty(MongoRepositoryTextSearchIntegrationTests.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mongo mongo() throws Exception {
|
||||
return new MongoClient();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class FullTextDocument {
|
||||
|
||||
private @Id String id;
|
||||
private @TextIndexed String title;
|
||||
private @TextIndexed String content;
|
||||
String nonTextIndexProperty;
|
||||
@TextScore Float score;
|
||||
|
||||
public FullTextDocument() {
|
||||
|
||||
}
|
||||
|
||||
public FullTextDocument(String id, String title, String content) {
|
||||
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(this.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof FullTextDocument)) {
|
||||
return false;
|
||||
}
|
||||
FullTextDocument other = (FullTextDocument) obj;
|
||||
return ObjectUtils.nullSafeEquals(this.id, other.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static interface FullTextRepository extends MongoRepository<FullTextDocument, String> {
|
||||
|
||||
List<FullTextDocument> findByNonTextIndexProperty(String nonTextIndexProperty, TextCriteria criteria);
|
||||
|
||||
List<FullTextDocument> findByNonTextIndexPropertyIsNullOrderByScoreDesc(TextCriteria criteria);
|
||||
|
||||
List<FullTextDocument> findByTitle(String title);
|
||||
|
||||
List<FullTextDocument> findAllBy(TextCriteria textCriteria);
|
||||
|
||||
List<FullTextDocument> findAllBy(TextCriteria textCriteria, Sort sort);
|
||||
|
||||
Page<FullTextDocument> findAllBy(TextCriteria textCriteria, Pageable pageable);
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,13 @@ import static org.junit.Assert.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.core.IsNull;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Metrics;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
* Unit tests for {@link MongoParametersParameterAccessor}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoParametersParameterAccessorUnitTests {
|
||||
|
||||
@@ -64,10 +67,41 @@ public class MongoParametersParameterAccessorUnitTests {
|
||||
assertThat(accessor.getMaxDistance(), is(DISTANCE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
|
||||
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
|
||||
|
||||
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
|
||||
new Point(10, 20), DISTANCE });
|
||||
assertThat(accessor.getFullText(), IsNull.nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class);
|
||||
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
|
||||
|
||||
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { "spring",
|
||||
TextCriteria.forDefaultLanguage().matching("data") });
|
||||
assertThat(accessor.getFullText().getCriteriaObject().toString(),
|
||||
equalTo("{ \"$text\" : { \"$search\" : \"data\"}}"));
|
||||
}
|
||||
|
||||
interface PersonRepository extends Repository<Person, Long> {
|
||||
|
||||
List<Person> findByLocationNear(Point point);
|
||||
|
||||
List<Person> findByLocationNear(Point point, Distance distance);
|
||||
|
||||
List<Person> findByFirstname(String firstname, TextCriteria fullText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.repository.Near;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.data.repository.query.Parameter;
|
||||
* Unit tests for {@link MongoParameters}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MongoParametersUnitTests {
|
||||
@@ -97,6 +99,28 @@ public class MongoParametersUnitTests {
|
||||
assertThat(parameters.getNearIndex(), is(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class);
|
||||
MongoParameters parameters = new MongoParameters(method, false);
|
||||
assertThat(parameters.getFullTextParameterIndex(), is(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class);
|
||||
MongoParameters parameters = new MongoParameters(method, false);
|
||||
assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter(), is(true));
|
||||
}
|
||||
|
||||
interface PersonRepository {
|
||||
|
||||
List<Person> findByLocationNear(Point point, Distance distance);
|
||||
@@ -110,5 +134,7 @@ public class MongoParametersUnitTests {
|
||||
GeoResults<Person> findByOtherLocationAndLocationNear(Point point, @Near Point anotherLocation);
|
||||
|
||||
GeoResults<Person> validDoubleArrays(double[] first, @Near double[] second);
|
||||
|
||||
List<Person> findByNameAndText(String name, TextCriteria text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
* Unit test for {@link MongoQueryMethod}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoQueryMethodUnitTests {
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.query.IsTextQuery.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -36,6 +37,8 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
@@ -120,6 +123,18 @@ public class PartTreeMongoQueryUnitTests {
|
||||
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMOGO-973
|
||||
*/
|
||||
@Test
|
||||
public void shouldAddFullTextParamCorrectlyToDerivedQuery() {
|
||||
|
||||
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstname",
|
||||
new Object[] { "text", TextCriteria.forDefaultLanguage().matching("search") });
|
||||
|
||||
assertThat(query, isTextQuery().searchingFor("search").where(new Criteria("firstname").is("text")));
|
||||
}
|
||||
|
||||
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) {
|
||||
|
||||
Class<?>[] types = new Class<?>[args.length];
|
||||
@@ -162,5 +177,7 @@ public class PartTreeMongoQueryUnitTests {
|
||||
|
||||
@Query(fields = "{ 'firstname' : 0, 'lastname' : 0 }")
|
||||
Person findPersonByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
Person findPersonByFirstname(String firstname, TextCriteria fullText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.query.TextCriteria;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
/**
|
||||
@@ -105,4 +106,13 @@ class StubParameterAccessor implements MongoParameterAccessor {
|
||||
public Point getGeoNearLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText()
|
||||
*/
|
||||
@Override
|
||||
public TextCriteria getFullText() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,45 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
|
||||
We think you will find this an extremely powerful tool for writing MongoDB queries.
|
||||
|
||||
[[mongodb.repositories.queries.full-text]]
|
||||
=== Full-text search queries
|
||||
MongoDBs full text search feature is very store specic and therefore can rather be found on `MongoRepository` than on the more general `CrudRepository`. What we need is a document with a full-text index defined for (Please see section <<mapping-usage-indexes.text-index>> for creating).
|
||||
|
||||
Additional methods on `MongoRepository` take `TextCriteria` as input parameter. In addition to those explicit methods, it is also possible to add a `TextCriteria` derived repository method. The criteria will added as an additional `AND` criteria. Once the entity contains a `@TextScore` annotated property the documents full-text score will be retrieved. Furthermore the `@TextScore` annotated property will also make it possible to sort by the documents score.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Document
|
||||
class FullTextDocument {
|
||||
|
||||
@Id String id;
|
||||
@TextIndexed String title;
|
||||
@TextIndexed String content;
|
||||
@TextScore Float score;
|
||||
}
|
||||
|
||||
interface FullTextRepository extends Repository<FullTextDocument, String> {
|
||||
|
||||
// Execute a full-text search and define sorting dynamically
|
||||
List<FullTextDocument> findAllBy(TextCriteria criteria, Sort sort);
|
||||
|
||||
// Paginate over a full-text search result
|
||||
Page<FullTextDocument> findAllBy(TextCriteria criteria, Pageable pageable);
|
||||
|
||||
// Combine a derived query with a full-text search
|
||||
List<FullTextDocument> findByTitleOrderByScoreDesc(String title, TextCriteria criteria);
|
||||
}
|
||||
|
||||
|
||||
Sort sort = new Sort("score");
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("spring", "data");
|
||||
List<FullTextDocument> result = repository.findAllBy(criteria, sort);
|
||||
|
||||
criteria = TextCriteria.forDefaultLanguage().matching("film");
|
||||
Page<FullTextDocument> page = repository.findAllBy(criteria, new PageRequest(1, 1, sort));
|
||||
List<FullTextDocument> result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);
|
||||
----
|
||||
|
||||
[[mongodb.repositories.misc]]
|
||||
== Miscellaneous
|
||||
|
||||
|
||||
Reference in New Issue
Block a user