Skip to content

Commit 151b1d4

Browse files
christophstroblodrotbohm
authored andcommitted
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.
1 parent 168cf3e commit 151b1d4

39 files changed

+994
-141
lines changed

spring-data-mongodb/Spring Data MongoDB.sonargraph

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<context version="7.1.9.205">
2+
<context version="7.1.10.209">
33
<scope type="Project" name="spring-data-mongodb">
44
<element type="TypeFilterReferenceOverridden" name="Filter">
55
<element type="IncludeTypePattern" name="org.springframework.data.mongodb.**"/>
@@ -32,6 +32,7 @@
3232
<element type="IncludeTypePattern" name="**.config.**"/>
3333
</element>
3434
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
35+
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API" type="AllowedDependency"/>
3536
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Implementation" type="AllowedDependency"/>
3637
</element>
3738
<dependency toName="Project|spring-data-mongodb::Layer|Config" type="AllowedDependency"/>

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,8 @@ protected <S, T> List<T> doFind(String collectionName, DBObject query, DBObject
16161616
CursorPreparer preparer, DbObjectCallback<T> objectCallback) {
16171617

16181618
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
1619-
DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
1619+
1620+
DBObject mappedFields = queryMapper.getMappedFields(fields, entity);
16201621
DBObject mappedQuery = queryMapper.getMappedObject(query, entity);
16211622

16221623
if (LOGGER.isDebugEnabled()) {
@@ -1969,8 +1970,7 @@ private DBObject getMappedSortObject(Query query, Class<?> type) {
19691970
return null;
19701971
}
19711972

1972-
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
1973-
return queryMapper.getMappedObject(query.getSortObject(), entity);
1973+
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
19741974
}
19751975

19761976
// Callback implementations
@@ -2030,7 +2030,7 @@ public FindCallback(DBObject query, DBObject fields) {
20302030
}
20312031

20322032
public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException {
2033-
if (fields == null) {
2033+
if (fields == null || fields.toMap().isEmpty()) {
20342034
return collection.find(query);
20352035
} else {
20362036
return collection.find(query, fields);

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter;
4646
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter;
4747
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToURLConverter;
48+
import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter;
4849
import org.springframework.data.mongodb.core.convert.MongoConverters.URLToStringConverter;
4950
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
5051
import org.springframework.util.Assert;
@@ -58,6 +59,7 @@
5859
*
5960
* @author Oliver Gierke
6061
* @author Thomas Darimont
62+
* @author Christoph Strobl
6163
*/
6264
public class CustomConversions {
6365

@@ -106,7 +108,8 @@ public CustomConversions(List<?> converters) {
106108
toRegister.add(URLToStringConverter.INSTANCE);
107109
toRegister.add(StringToURLConverter.INSTANCE);
108110
toRegister.add(DBObjectToStringConverter.INSTANCE);
109-
111+
toRegister.add(TermToStringConverter.INSTANCE);
112+
110113
toRegister.addAll(JodaTimeConverters.getConvertersToRegister());
111114
toRegister.addAll(GeoConverters.getConvertersToRegister());
112115

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.springframework.core.convert.TypeDescriptor;
2626
import org.springframework.core.convert.converter.Converter;
2727
import org.springframework.data.convert.ReadingConverter;
28+
import org.springframework.data.convert.WritingConverter;
29+
import org.springframework.data.mongodb.core.query.Term;
2830
import org.springframework.util.StringUtils;
2931

3032
import com.mongodb.DBObject;
@@ -34,6 +36,7 @@
3436
*
3537
* @author Oliver Gierke
3638
* @author Thomas Darimont
39+
* @author Christoph Strobl
3740
*/
3841
abstract class MongoConverters {
3942

@@ -160,4 +163,19 @@ public String convert(DBObject source) {
160163
return source == null ? null : source.toString();
161164
}
162165
}
166+
167+
/**
168+
* @author Christoph Strobl
169+
* @since 1.6
170+
*/
171+
@WritingConverter
172+
public static enum TermToStringConverter implements Converter<Term, String> {
173+
174+
INSTANCE;
175+
176+
@Override
177+
public String convert(Term source) {
178+
return source == null ? null : source.getFormatted();
179+
}
180+
}
163181
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
public class QueryMapper {
5858

5959
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
60+
private static final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
61+
62+
private enum MetaMapping {
63+
FORCE, WHEN_PRESENT, IGNORE;
64+
}
6065

6166
private final ConversionService conversionService;
6267
private final MongoConverter converter;
@@ -119,6 +124,61 @@ public DBObject getMappedObject(DBObject query, MongoPersistentEntity<?> entity)
119124
return result;
120125
}
121126

127+
/**
128+
* Maps fields used for sorting to the {@link MongoPersistentEntity}s properties. <br />
129+
* Also converts properties to their {@code $meta} representation if present.
130+
*
131+
* @param sortObject
132+
* @param entity
133+
* @return
134+
* @since 1.6
135+
*/
136+
public DBObject getMappedSort(DBObject sortObject, MongoPersistentEntity<?> entity) {
137+
138+
if (sortObject == null) {
139+
return null;
140+
}
141+
142+
DBObject mappedSort = getMappedObject(sortObject, entity);
143+
mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT);
144+
return mappedSort;
145+
}
146+
147+
/**
148+
* Maps fields to retrieve to the {@link MongoPersistentEntity}s properties. <br />
149+
* Also onverts and potentially adds missing property {@code $meta} representation.
150+
*
151+
* @param fieldsObject
152+
* @param entity
153+
* @return
154+
* @since 1.6
155+
*/
156+
public DBObject getMappedFields(DBObject fieldsObject, MongoPersistentEntity<?> entity) {
157+
158+
DBObject mappedFields = fieldsObject != null ? getMappedObject(fieldsObject, entity) : new BasicDBObject();
159+
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
160+
return mappedFields.keySet().isEmpty() ? null : mappedFields;
161+
}
162+
163+
private void mapMetaAttributes(DBObject source, MongoPersistentEntity<?> entity, MetaMapping metaMapping) {
164+
165+
if (entity == null || source == null) {
166+
return;
167+
}
168+
169+
if (entity.hasTextScoreProperty() && !MetaMapping.IGNORE.equals(metaMapping)) {
170+
MongoPersistentProperty textScoreProperty = entity.getTextScoreProperty();
171+
if (MetaMapping.FORCE.equals(metaMapping)
172+
|| (MetaMapping.WHEN_PRESENT.equals(metaMapping) && source.containsField(textScoreProperty.getFieldName()))) {
173+
source.putAll(getMappedTextScoreField(textScoreProperty));
174+
}
175+
}
176+
}
177+
178+
private DBObject getMappedTextScoreField(MongoPersistentProperty property) {
179+
return new BasicDBObject(property.getFieldName(), META_TEXT_SCORE);
180+
}
181+
122182
/**
123183
* Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account
124184
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ public TextIndexDefinitionBuilder onFields(String... fieldnames) {
276276
* @return
277277
*/
278278
public TextIndexDefinitionBuilder onField(String fieldname) {
279-
return onField(fieldname, Float.NaN);
279+
return onField(fieldname, 1F);
280280
}
281281

282282
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ public String getLanguage() {
114114
return this.language;
115115
}
116116

117+
/*
118+
* (non-Javadoc)
119+
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getTextScoreProperty()
120+
*/
121+
@Override
122+
public MongoPersistentProperty getTextScoreProperty() {
123+
return getPersistentProperty(TextScore.class);
124+
}
125+
126+
/*
127+
* (non-Javadoc)
128+
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#hasTextScoreProperty()
129+
*/
130+
@Override
131+
public boolean hasTextScoreProperty() {
132+
return getTextScoreProperty() != null;
133+
}
134+
117135
/*
118136
* (non-Javadoc)
119137
* @see org.springframework.data.mapping.model.BasicPersistentEntity#verify()

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,21 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
4040
*/
4141
String getLanguage();
4242

43+
/**
44+
* Returns the property holding text score value.
45+
*
46+
* @since 1.6
47+
* @see #hasTextScoreProperty()
48+
* @return {@literal null} if not present.
49+
*/
50+
MongoPersistentProperty getTextScoreProperty();
51+
52+
/**
53+
* Returns whether the entity has a {@link TextScore} property.
54+
*
55+
* @since 1.6
56+
* @return true if property annotated with {@link TextScore} is present.
57+
*/
58+
boolean hasTextScoreProperty();
59+
4360
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/BasicQuery.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ public BasicQuery(DBObject queryObject, DBObject fieldsObject) {
5050
this.fieldsObject = fieldsObject;
5151
}
5252

53+
/*
54+
* (non-Javadoc)
55+
* @see org.springframework.data.mongodb.core.query.Query#addCriteria(org.springframework.data.mongodb.core.query.CriteriaDefinition)
56+
*/
5357
@Override
54-
public Query addCriteria(Criteria criteria) {
58+
public Query addCriteria(CriteriaDefinition criteria) {
5559
this.queryObject.putAll(criteria.getCriteriaObject());
5660
return this;
5761
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/CriteriaDefinition.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2011 the original author or authors.
2+
* Copyright 2010-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,25 @@
1717

1818
import com.mongodb.DBObject;
1919

20+
/**
21+
* @author Oliver Gierke
22+
* @author Christoph Strobl
23+
*/
2024
public interface CriteriaDefinition {
2125

26+
/**
27+
* Get {@link DBObject} representation.
28+
*
29+
* @return
30+
*/
2231
DBObject getCriteriaObject();
2332

24-
}
33+
/**
34+
* Get the identifying {@literal key}.
35+
*
36+
* @return
37+
* @since 1.6
38+
*/
39+
String getKey();
40+
41+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,6 @@ public Integer getSkip() {
343343
*
344344
* @return
345345
*/
346-
@SuppressWarnings("deprecation")
347346
public DBObject toDBObject() {
348347

349348
BasicDBObject dbObject = new BasicDBObject();

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,56 +39,63 @@
3939
* @author Thomas Risberg
4040
* @author Oliver Gierke
4141
* @author Thomas Darimont
42+
* @author Christoph Strobl
4243
*/
4344
public class Query {
4445

4546
private static final String RESTRICTED_TYPES_KEY = "_$RESTRICTED_TYPES";
4647

4748
private final Set<Class<?>> restrictedTypes = new HashSet<Class<?>>();
48-
private final Map<String, Criteria> criteria = new LinkedHashMap<String, Criteria>();
49+
private final Map<String, CriteriaDefinition> criteria = new LinkedHashMap<String, CriteriaDefinition>();
4950
private Field fieldSpec;
5051
private Sort sort;
5152
private int skip;
5253
private int limit;
5354
private String hint;
5455

5556
/**
56-
* Static factory method to create a {@link Query} using the provided {@link Criteria}.
57+
* Static factory method to create a {@link Query} using the provided {@link CriteriaDefinition}.
5758
*
58-
* @param criteria must not be {@literal null}.
59+
* @param criteriaDefinition must not be {@literal null}.
5960
* @return
61+
* @since 1.6
6062
*/
61-
public static Query query(Criteria criteria) {
62-
return new Query(criteria);
63+
public static Query query(CriteriaDefinition criteriaDefinition) {
64+
return new Query(criteriaDefinition);
6365
}
6466

6567
public Query() {}
6668

6769
/**
68-
* Creates a new {@link Query} using the given {@link Criteria}.
70+
* Creates a new {@link Query} using the given {@link CriteriaDefinition}.
6971
*
70-
* @param criteria must not be {@literal null}.
72+
* @param criteriaDefinition must not be {@literal null}.
73+
* @since 1.6
7174
*/
72-
public Query(Criteria criteria) {
73-
addCriteria(criteria);
75+
public Query(CriteriaDefinition criteriaDefinition) {
76+
addCriteria(criteriaDefinition);
7477
}
7578

7679
/**
77-
* Adds the given {@link Criteria} to the current {@link Query}.
80+
* Adds the given {@link CriteriaDefinition} to the current {@link Query}.
7881
*
79-
* @param criteria must not be {@literal null}.
82+
* @param criteriaDefinition must not be {@literal null}.
8083
* @return
84+
* @since 1.6
8185
*/
82-
public Query addCriteria(Criteria criteria) {
83-
CriteriaDefinition existing = this.criteria.get(criteria.getKey());
84-
String key = criteria.getKey();
86+
public Query addCriteria(CriteriaDefinition criteriaDefinition) {
87+
88+
CriteriaDefinition existing = this.criteria.get(criteriaDefinition.getKey());
89+
String key = criteriaDefinition.getKey();
90+
8591
if (existing == null) {
86-
this.criteria.put(key, criteria);
92+
this.criteria.put(key, criteriaDefinition);
8793
} else {
8894
throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, "
8995
+ "you can't add a second '" + key + "' criteria. " + "Query already contains '"
9096
+ existing.getCriteriaObject() + "'.");
9197
}
98+
9299
return this;
93100
}
94101

@@ -268,8 +275,8 @@ public String getHint() {
268275
return hint;
269276
}
270277

271-
protected List<Criteria> getCriteria() {
272-
return new ArrayList<Criteria>(this.criteria.values());
278+
protected List<CriteriaDefinition> getCriteria() {
279+
return new ArrayList<CriteriaDefinition>(this.criteria.values());
273280
}
274281

275282
/*

0 commit comments

Comments
 (0)