Skip to content

Commit e0c3bb5

Browse files
committed
Query: support negative queries, closes #44.
1 parent ba0972c commit e0c3bb5

12 files changed

+98
-23
lines changed

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ public FieldMappers fullName(String fullName) {
111111
return fullNameFieldMappers.get(fullName);
112112
}
113113

114+
/**
115+
* Tries to find first based on {@link #fullName(String)}, then by {@link #indexName(String)}.
116+
*/
117+
public FieldMappers smartName(String name) {
118+
FieldMappers fieldMappers = fullName(name);
119+
if (fieldMappers != null) {
120+
return fieldMappers;
121+
}
122+
return indexName(name);
123+
}
124+
114125
/**
115126
* A smart analyzer used for indexing that takes into account specific analyzers configured
116127
* per {@link FieldMapper}.

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
package org.elasticsearch.index.mapper;
2121

2222
import org.apache.lucene.analysis.Analyzer;
23+
import org.apache.lucene.document.Fieldable;
2324
import org.elasticsearch.util.Nullable;
2425
import org.elasticsearch.util.concurrent.ThreadSafe;
2526

2627
/**
27-
* @author kimchy (Shay Banon)
28+
* @author kimchy (shay.banon)
2829
*/
2930
@ThreadSafe
3031
public interface DocumentMapper {
@@ -73,12 +74,20 @@ public interface DocumentMapper {
7374

7475
/**
7576
* Parses the source into a parsed document.
76-
* <p/>
77+
*
7778
* <p>Validates that the source has the provided id and type. Note, most times
7879
* we will already have the id and the type even though they exist in the source as well.
7980
*/
8081
ParsedDocument parse(@Nullable String type, @Nullable String id, byte[] source) throws MapperParsingException;
8182

83+
/**
84+
* Parses the source into a parsed document.
85+
*
86+
* <p>Validates that the source has the provided id and type. Note, most times
87+
* we will already have the id and the type even though they exist in the source as well.
88+
*/
89+
ParsedDocument parse(@Nullable String type, @Nullable String id, byte[] source, @Nullable ParseListener listener) throws MapperParsingException;
90+
8291
/**
8392
* Parses the source into the parsed document.
8493
*/
@@ -120,4 +129,25 @@ public MergeFlags ignoreDuplicates(boolean ignoreDuplicates) {
120129
return this;
121130
}
122131
}
132+
133+
/**
134+
* A listener to be called during the parse process.
135+
*/
136+
public static interface ParseListener<ParseContext> {
137+
138+
public static final ParseListener EMPTY = new ParseListenerAdapter();
139+
140+
/**
141+
* Called before a field is added to the document. Return <tt>true</tt> to include
142+
* it in the document.
143+
*/
144+
boolean beforeFieldAdded(FieldMapper fieldMapper, Fieldable fieldable, ParseContext parseContent);
145+
}
146+
147+
public static class ParseListenerAdapter implements ParseListener {
148+
149+
@Override public boolean beforeFieldAdded(FieldMapper fieldMapper, Fieldable fieldable, Object parseContext) {
150+
return true;
151+
}
152+
}
123153
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/FieldMapperListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
package org.elasticsearch.index.mapper;
2121

2222
/**
23-
* @author kimchy (Shay Banon)
23+
* @author kimchy (shay.banon)
2424
*/
2525
public interface FieldMapperListener {
2626

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperService.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,7 @@ public FieldMappers smartNameFieldMappers(String smartName) {
249249
DocumentMapper possibleDocMapper = mappers.get(possibleType);
250250
if (possibleDocMapper != null) {
251251
String possibleName = smartName.substring(dotIndex + 1);
252-
FieldMappers mappers = possibleDocMapper.mappers().fullName(possibleName);
253-
if (mappers != null) {
254-
return mappers;
255-
}
256-
mappers = possibleDocMapper.mappers().indexName(possibleName);
252+
FieldMappers mappers = possibleDocMapper.mappers().smartName(possibleName);
257253
if (mappers != null) {
258254
return mappers;
259255
}
@@ -284,11 +280,7 @@ public SmartNameFieldMappers smartName(String smartName) {
284280
DocumentMapper possibleDocMapper = mappers.get(possibleType);
285281
if (possibleDocMapper != null) {
286282
String possibleName = smartName.substring(dotIndex + 1);
287-
FieldMappers mappers = possibleDocMapper.mappers().fullName(possibleName);
288-
if (mappers != null) {
289-
return new SmartNameFieldMappers(mappers, possibleDocMapper);
290-
}
291-
mappers = possibleDocMapper.mappers().indexName(possibleName);
283+
FieldMappers mappers = possibleDocMapper.mappers().smartName(possibleName);
292284
if (mappers != null) {
293285
return new SmartNameFieldMappers(mappers, possibleDocMapper);
294286
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDocumentMapper.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,11 @@ void mappingSource(String mappingSource) {
251251
return parse(null, null, source);
252252
}
253253

254-
@Override public ParsedDocument parse(String type, String id, byte[] source) {
254+
@Override public ParsedDocument parse(@Nullable String type, @Nullable String id, byte[] source) throws MapperParsingException {
255+
return parse(type, id, source, ParseListener.EMPTY);
256+
}
257+
258+
@Override public ParsedDocument parse(String type, String id, byte[] source, ParseListener listener) {
255259
JsonParseContext jsonContext = cache.get();
256260

257261
if (type != null && !type.equals(this.type)) {
@@ -262,7 +266,7 @@ void mappingSource(String mappingSource) {
262266
JsonParser jp = null;
263267
try {
264268
jp = jsonFactory.createJsonParser(source);
265-
jsonContext.reset(jp, new Document(), type, source);
269+
jsonContext.reset(jp, new Document(), type, source, listener);
266270

267271
// will result in JsonToken.START_OBJECT
268272
JsonToken token = jp.nextToken();

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFieldMapper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ protected JsonFieldMapper(Names names, Field.Index index, Field.Store store, Fie
270270
field.setOmitNorms(omitNorms);
271271
field.setOmitTermFreqAndPositions(omitTermFreqAndPositions);
272272
field.setBoost(boost);
273-
jsonContext.doc().add(field);
273+
if (jsonContext.listener().beforeFieldAdded(this, field, jsonContext)) {
274+
jsonContext.doc().add(field);
275+
}
274276
}
275277

276278
protected abstract Field parseCreateField(JsonParseContext jsonContext) throws IOException;

modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonParseContext.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.lucene.document.Document;
2323
import org.codehaus.jackson.JsonParser;
24+
import org.elasticsearch.index.mapper.DocumentMapper;
2425
import org.elasticsearch.util.concurrent.NotThreadSafe;
2526

2627
/**
@@ -43,6 +44,8 @@ public class JsonParseContext {
4344

4445
private String id;
4546

47+
private DocumentMapper.ParseListener listener;
48+
4649
private String uid;
4750

4851
private StringBuilder stringBuilder = new StringBuilder();
@@ -56,14 +59,15 @@ public JsonParseContext(JsonDocumentMapper docMapper, JsonPath path) {
5659
this.path = path;
5760
}
5861

59-
public void reset(JsonParser jsonParser, Document document, String type, byte[] source) {
62+
public void reset(JsonParser jsonParser, Document document, String type, byte[] source, DocumentMapper.ParseListener listener) {
6063
this.jsonParser = jsonParser;
6164
this.document = document;
6265
this.type = type;
6366
this.source = source;
6467
this.path.reset();
6568
this.parsedIdState = ParsedIdState.NO;
6669
this.mappersAdded = false;
70+
this.listener = listener;
6771
}
6872

6973
public boolean mappersAdded() {
@@ -90,6 +94,10 @@ public JsonParser jp() {
9094
return this.jsonParser;
9195
}
9296

97+
public DocumentMapper.ParseListener listener() {
98+
return this.listener;
99+
}
100+
93101
public Document doc() {
94102
return this.document;
95103
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonQueryParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.List;
3636

3737
import static com.google.common.collect.Lists.*;
38+
import static org.elasticsearch.index.query.support.QueryParsers.*;
3839

3940
/**
4041
* @author kimchy (Shay Banon)
@@ -110,6 +111,6 @@ public class BoolJsonQueryParser extends AbstractIndexComponent implements JsonQ
110111
if (minimumNumberShouldMatch != -1) {
111112
query.setMinimumNumberShouldMatch(minimumNumberShouldMatch);
112113
}
113-
return query;
114+
return fixNegativeQueryIfNeeded(query);
114115
}
115116
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/JsonQueryBuilders.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,11 @@ public static ConstantScoreQueryJsonQueryBuilder constantScoreQuery(JsonFilterBu
116116
return new ConstantScoreQueryJsonQueryBuilder(filterBuilder);
117117
}
118118

119-
public static MoreLikeThisJsonQueryBuilder moreLikeThis(String... fields) {
119+
public static MoreLikeThisJsonQueryBuilder moreLikeThisQuery(String... fields) {
120120
return new MoreLikeThisJsonQueryBuilder(fields);
121121
}
122122

123-
public static MoreLikeThisFieldJsonQueryBuilder moreLikeThisField(String name) {
123+
public static MoreLikeThisFieldJsonQueryBuilder moreLikeThisFieldQuery(String name) {
124124
return new MoreLikeThisFieldJsonQueryBuilder(name);
125125
}
126126

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryStringJsonQueryParser.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737

3838
import java.io.IOException;
3939

40+
import static org.elasticsearch.index.query.support.QueryParsers.*;
41+
4042
/**
4143
* @author kimchy (Shay Banon)
4244
*/
@@ -154,7 +156,7 @@ public class QueryStringJsonQueryParser extends AbstractIndexComponent implement
154156
try {
155157
Query query = queryParser.parse(queryString);
156158
query.setBoost(boost);
157-
return query;
159+
return fixNegativeQueryIfNeeded(query);
158160
} catch (ParseException e) {
159161
throw new QueryParsingException(index, "Failed to parse query [" + queryString + "]", e);
160162
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/support/QueryParsers.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.elasticsearch.util.Nullable;
2727
import org.elasticsearch.util.lucene.search.TermFilter;
2828

29+
import java.util.List;
30+
2931
/**
3032
* @author kimchy (Shay Banon)
3133
*/
@@ -35,6 +37,29 @@ private QueryParsers() {
3537

3638
}
3739

40+
public static boolean isNegativeQuery(Query q) {
41+
if (!(q instanceof BooleanQuery)) {
42+
return false;
43+
}
44+
List<BooleanClause> clauses = ((BooleanQuery) q).clauses();
45+
if (clauses.isEmpty()) {
46+
return false;
47+
}
48+
for (BooleanClause clause : clauses) {
49+
if (!clause.isProhibited()) return false;
50+
}
51+
return true;
52+
}
53+
54+
public static Query fixNegativeQueryIfNeeded(Query q) {
55+
if (isNegativeQuery(q)) {
56+
BooleanQuery newBq = (BooleanQuery) q.clone();
57+
newBq.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
58+
return newBq;
59+
}
60+
return q;
61+
}
62+
3863
public static Query wrapSmartNameQuery(Query query, @Nullable MapperService.SmartNameFieldMappers smartFieldMappers,
3964
@Nullable FilterCache filterCache) {
4065
if (smartFieldMappers == null) {

modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/SimpleJsonIndexQueryParserTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ public class SimpleJsonIndexQueryParserTests {
603603

604604
@Test public void testMoreLikeThisBuilder() throws Exception {
605605
IndexQueryParser queryParser = newQueryParser();
606-
Query parsedQuery = queryParser.parse(moreLikeThis("name.first", "name.last").likeText("something").minTermFrequency(1).maxQueryTerms(12));
606+
Query parsedQuery = queryParser.parse(moreLikeThisQuery("name.first", "name.last").likeText("something").minTermFrequency(1).maxQueryTerms(12));
607607
assertThat(parsedQuery, instanceOf(MoreLikeThisQuery.class));
608608
MoreLikeThisQuery mltQuery = (MoreLikeThisQuery) parsedQuery;
609609
assertThat(mltQuery.getMoreLikeFields()[0], equalTo("name.first"));
@@ -627,7 +627,7 @@ public class SimpleJsonIndexQueryParserTests {
627627

628628
@Test public void testMoreLikeThisFieldBuilder() throws Exception {
629629
IndexQueryParser queryParser = newQueryParser();
630-
Query parsedQuery = queryParser.parse(moreLikeThisField("name.first").likeText("something").minTermFrequency(1).maxQueryTerms(12));
630+
Query parsedQuery = queryParser.parse(moreLikeThisFieldQuery("name.first").likeText("something").minTermFrequency(1).maxQueryTerms(12));
631631
assertThat(parsedQuery, instanceOf(MoreLikeThisQuery.class));
632632
MoreLikeThisQuery mltQuery = (MoreLikeThisQuery) parsedQuery;
633633
assertThat(mltQuery.getMoreLikeFields()[0], equalTo("name.first"));

0 commit comments

Comments
 (0)