Skip to content

Commit daade44

Browse files
authored
Share same existsQuery impl throughout mappers (#57607)
Most of our field types have the same implementation for their `existsQuery` method which relies on doc_values if present, otherwise it queries norms if available or uses a term query against the _field_names meta field. This standard implementation is repeated in many different mappers. There are field types that only query doc_values, because they always have them, and field types that always query _field_names, because they never have norms nor doc_values. We could apply the same standard logic to all of these field types as `MappedFieldType` has the knowledge about what data structures are available. This commit introduces a standard implementation that does the right thing depending on the data structure that is available. With that only field types that require a different behaviour need to override the existsQuery method. At the same time, this no longer forces subclasses to override `existsQuery`, which could be forgotten when needed. To address this we introduced a new test method in `MapperTestCase` that verifies the `existsQuery` being generated and its consistency with the available data structures.
1 parent 2d64d4b commit daade44

File tree

63 files changed

+495
-400
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+495
-400
lines changed

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@
2424
import org.apache.lucene.index.LeafReaderContext;
2525
import org.apache.lucene.index.NumericDocValues;
2626
import org.apache.lucene.index.SortedNumericDocValues;
27-
import org.apache.lucene.index.Term;
2827
import org.apache.lucene.search.BoostQuery;
29-
import org.apache.lucene.search.DocValuesFieldExistsQuery;
3028
import org.apache.lucene.search.Query;
31-
import org.apache.lucene.search.TermQuery;
3229
import org.apache.lucene.util.BytesRef;
3330
import org.elasticsearch.common.Explicit;
3431
import org.elasticsearch.common.settings.Setting;
@@ -156,15 +153,6 @@ public String typeName() {
156153
return CONTENT_TYPE;
157154
}
158155

159-
@Override
160-
public Query existsQuery(QueryShardContext context) {
161-
if (hasDocValues()) {
162-
return new DocValuesFieldExistsQuery(name());
163-
} else {
164-
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
165-
}
166-
}
167-
168156
@Override
169157
public Query termQuery(Object value, QueryShardContext context) {
170158
failIfNotIndexed();

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.apache.lucene.search.BooleanQuery;
3737
import org.apache.lucene.search.ConstantScoreQuery;
3838
import org.apache.lucene.search.MultiTermQuery;
39-
import org.apache.lucene.search.NormsFieldExistsQuery;
4039
import org.apache.lucene.search.PrefixQuery;
4140
import org.apache.lucene.search.Query;
4241
import org.apache.lucene.search.TermQuery;
@@ -271,15 +270,6 @@ private ShingleFieldType shingleFieldForPositions(int positions) {
271270
return shingleFields[Math.min(indexFromShingleSize, shingleFields.length - 1)];
272271
}
273272

274-
@Override
275-
public Query existsQuery(QueryShardContext context) {
276-
if (getTextSearchInfo().hasNorms() == false) {
277-
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
278-
} else {
279-
return new NormsFieldExistsQuery(name());
280-
}
281-
}
282-
283273
@Override
284274
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
285275
if (prefixField == null || prefixField.termLengthWithinBounds(value.length()) == false) {
@@ -500,15 +490,6 @@ public String typeName() {
500490
return CONTENT_TYPE;
501491
}
502492

503-
@Override
504-
public Query existsQuery(QueryShardContext context) {
505-
if (getTextSearchInfo().hasNorms() == false) {
506-
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
507-
} else {
508-
return new NormsFieldExistsQuery(name());
509-
}
510-
}
511-
512493
@Override
513494
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
514495
if (prefixFieldType == null || prefixFieldType.termLengthWithinBounds(value.length()) == false) {

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeatureFieldMapperTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.lucene.analysis.tokenattributes.TermFrequencyAttribute;
2424
import org.apache.lucene.document.FeatureField;
2525
import org.apache.lucene.index.IndexableField;
26+
import org.apache.lucene.search.Query;
27+
import org.apache.lucene.search.TermQuery;
2628
import org.elasticsearch.Version;
2729
import org.elasticsearch.cluster.metadata.IndexMetadata;
2830
import org.elasticsearch.common.Strings;
@@ -38,7 +40,15 @@
3840
import java.util.List;
3941
import java.util.Set;
4042

43+
import static org.hamcrest.Matchers.instanceOf;
44+
4145
public class RankFeatureFieldMapperTests extends FieldMapperTestCase2<RankFeatureFieldMapper.Builder> {
46+
47+
@Override
48+
protected void writeFieldValue(XContentBuilder builder) throws IOException {
49+
builder.value(10);
50+
}
51+
4252
@Override
4353
protected Set<String> unsupportedProperties() {
4454
return Set.of("analyzer", "similarity", "store", "doc_values", "index");
@@ -52,6 +62,15 @@ public void setup() {
5262
});
5363
}
5464

65+
@Override
66+
protected void assertExistsQuery(MappedFieldType fieldType, Query query, ParseContext.Document fields) {
67+
assertThat(query, instanceOf(TermQuery.class));
68+
TermQuery termQuery = (TermQuery) query;
69+
assertEquals("_feature", termQuery.getTerm().field());
70+
assertEquals("field", termQuery.getTerm().text());
71+
assertNotNull(fields.getField("_feature"));
72+
}
73+
5574
@Override
5675
protected Collection<? extends Plugin> getPlugins() {
5776
return List.of(new MapperExtrasPlugin());

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@
3434

3535
public class RankFeaturesFieldMapperTests extends FieldMapperTestCase2<RankFeaturesFieldMapper.Builder> {
3636

37+
@Override
38+
protected void writeFieldValue(XContentBuilder builder) throws IOException {
39+
builder.startObject().field("foo", 10).field("bar", 20).endObject();
40+
}
41+
42+
@Override
43+
protected void assertExistsQuery(MapperService mapperService) {
44+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> super.assertExistsQuery(mapperService));
45+
assertEquals("[rank_features] fields do not support [exists] queries", iae.getMessage());
46+
}
47+
3748
@Override
3849
protected Set<String> unsupportedProperties() {
3950
return Set.of("analyzer", "similarity", "store", "doc_values", "index");
@@ -58,7 +69,7 @@ public void testDefaults() throws Exception {
5869
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
5970
assertEquals(Strings.toString(fieldMapping(this::minimalMapping)), mapper.mappingSource().toString());
6071

61-
ParsedDocument doc1 = mapper.parse(source(b -> b.startObject("field").field("foo", 10).field("bar", 20).endObject()));
72+
ParsedDocument doc1 = mapper.parse(source(this::writeField));
6273

6374
IndexableField[] fields = doc1.rootDoc().getFields("field");
6475
assertEquals(2, fields.length);

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapperTests.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import java.util.List;
3838

3939
import static java.util.Collections.singletonList;
40-
import static org.elasticsearch.index.mapper.FieldMapperTestCase.fetchSourceValue;
4140
import static org.hamcrest.Matchers.containsString;
4241

4342
public class ScaledFloatFieldMapperTests extends MapperTestCase {
@@ -47,23 +46,31 @@ protected Collection<? extends Plugin> getPlugins() {
4746
return singletonList(new MapperExtrasPlugin());
4847
}
4948

49+
@Override
50+
protected void writeFieldValue(XContentBuilder builder) throws IOException {
51+
builder.value(123);
52+
}
53+
5054
@Override
5155
protected void minimalMapping(XContentBuilder b) throws IOException {
5256
b.field("type", "scaled_float").field("scaling_factor", 10.0);
5357
}
5458

59+
public void testExistsQueryDocValuesDisabled() throws IOException {
60+
MapperService mapperService = createMapperService(fieldMapping(b -> {
61+
minimalMapping(b);
62+
b.field("doc_values", false);
63+
}));
64+
assertExistsQuery(mapperService);
65+
assertParseMinimalWarnings();
66+
}
67+
5568
public void testDefaults() throws Exception {
5669
XContentBuilder mapping = fieldMapping(b -> b.field("type", "scaled_float").field("scaling_factor", 10.0));
5770
DocumentMapper mapper = createDocumentMapper(mapping);
5871
assertEquals(Strings.toString(mapping), mapper.mappingSource().toString());
5972

60-
ParsedDocument doc = mapper.parse(new SourceToParse("test", "1", BytesReference
61-
.bytes(XContentFactory.jsonBuilder()
62-
.startObject()
63-
.field("field", 123)
64-
.endObject()),
65-
XContentType.JSON));
66-
73+
ParsedDocument doc = mapper.parse(source(b -> b.field("field", 123)));
6774
IndexableField[] fields = doc.rootDoc().getFields("field");
6875
assertEquals(2, fields.length);
6976
IndexableField pointField = fields[0];

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapperTests.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@
8080

8181
public class SearchAsYouTypeFieldMapperTests extends FieldMapperTestCase2<SearchAsYouTypeFieldMapper.Builder> {
8282

83+
@Override
84+
protected void writeFieldValue(XContentBuilder builder) throws IOException {
85+
builder.value("new york city");
86+
}
87+
8388
@Before
8489
public void addModifiers() {
8590
addModifier("max_shingle_size", false, (a, b) -> {
@@ -171,7 +176,7 @@ public void testConfiguration() throws IOException {
171176
fieldMapping(
172177
b -> b.field("type", "search_as_you_type").field("analyzer", analyzerName).field("max_shingle_size", maxShingleSize)
173178
)
174-
);
179+
);
175180

176181
SearchAsYouTypeFieldMapper rootMapper = getRootFieldMapper(defaultMapper, "field");
177182
assertRootFieldMapper(rootMapper, maxShingleSize, analyzerName);

modules/parent-join/src/main/java/org/elasticsearch/join/mapper/MetaJoinFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public static class MetaJoinFieldType extends StringFieldType {
8181

8282
private final String joinField;
8383

84-
MetaJoinFieldType(String joinField) {
84+
private MetaJoinFieldType(String joinField) {
8585
super(NAME, false, false, false, TextSearchInfo.SIMPLE_MATCH_ONLY, Collections.emptyMap());
8686
this.joinField = joinField;
8787
}

modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentIdFieldMapper.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.apache.lucene.search.BooleanClause;
2828
import org.apache.lucene.search.BooleanQuery;
2929
import org.apache.lucene.search.ConstantScoreQuery;
30-
import org.apache.lucene.search.DocValuesFieldExistsQuery;
3130
import org.apache.lucene.search.Query;
3231
import org.apache.lucene.search.TermQuery;
3332
import org.apache.lucene.util.BytesRef;
@@ -41,7 +40,6 @@
4140
import org.elasticsearch.index.mapper.StringFieldType;
4241
import org.elasticsearch.index.mapper.TextSearchInfo;
4342
import org.elasticsearch.index.mapper.ValueFetcher;
44-
import org.elasticsearch.index.query.QueryShardContext;
4543
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
4644
import org.elasticsearch.search.lookup.SearchLookup;
4745

@@ -98,7 +96,7 @@ public ParentIdFieldMapper build(BuilderContext context) {
9896
}
9997

10098
public static final class ParentIdFieldType extends StringFieldType {
101-
ParentIdFieldType(String name, boolean eagerGlobalOrdinals, Map<String, String> meta) {
99+
private ParentIdFieldType(String name, boolean eagerGlobalOrdinals, Map<String, String> meta) {
102100
super(name, true, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
103101
setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
104102
setEagerGlobalOrdinals(eagerGlobalOrdinals);
@@ -123,11 +121,6 @@ public Object valueForDisplay(Object value) {
123121
BytesRef binaryValue = (BytesRef) value;
124122
return binaryValue.utf8ToString();
125123
}
126-
127-
@Override
128-
public Query existsQuery(QueryShardContext context) {
129-
return new DocValuesFieldExistsQuery(name());
130-
}
131124
}
132125

133126
private final String parentName;

modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import org.apache.lucene.document.FieldType;
2424
import org.apache.lucene.document.SortedDocValuesField;
2525
import org.apache.lucene.index.IndexOptions;
26-
import org.apache.lucene.search.DocValuesFieldExistsQuery;
27-
import org.apache.lucene.search.Query;
2826
import org.apache.lucene.util.BytesRef;
2927
import org.elasticsearch.common.lucene.Lucene;
3028
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -46,7 +44,6 @@
4644
import org.elasticsearch.index.mapper.StringFieldType;
4745
import org.elasticsearch.index.mapper.TextSearchInfo;
4846
import org.elasticsearch.index.mapper.ValueFetcher;
49-
import org.elasticsearch.index.query.QueryShardContext;
5047
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
5148
import org.elasticsearch.search.lookup.SearchLookup;
5249

@@ -209,7 +206,7 @@ public Mapper.Builder<?> parse(String name, Map<String, Object> node, ParserCont
209206
}
210207

211208
public static final class JoinFieldType extends StringFieldType {
212-
public JoinFieldType(String name, Map<String, String> meta) {
209+
private JoinFieldType(String name, Map<String, String> meta) {
213210
super(name, true, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
214211
setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
215212
}
@@ -233,11 +230,6 @@ public Object valueForDisplay(Object value) {
233230
BytesRef binaryValue = (BytesRef) value;
234231
return binaryValue.utf8ToString();
235232
}
236-
237-
@Override
238-
public Query existsQuery(QueryShardContext context) {
239-
return new DocValuesFieldExistsQuery(name());
240-
}
241233
}
242234

243235
// The meta field that ensures that there is no other parent-join in the mapping

modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.apache.lucene.search.BooleanClause;
3434
import org.apache.lucene.search.BooleanQuery;
3535
import org.apache.lucene.search.CoveringQuery;
36-
import org.apache.lucene.search.DocValuesFieldExistsQuery;
3736
import org.apache.lucene.search.IndexSearcher;
3837
import org.apache.lucene.search.LongValuesSource;
3938
import org.apache.lucene.search.MatchNoDocsQuery;
@@ -57,7 +56,6 @@
5756
import org.elasticsearch.common.xcontent.XContentType;
5857
import org.elasticsearch.index.mapper.BinaryFieldMapper;
5958
import org.elasticsearch.index.mapper.FieldMapper;
60-
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
6159
import org.elasticsearch.index.mapper.KeywordFieldMapper;
6260
import org.elasticsearch.index.mapper.MappedFieldType;
6361
import org.elasticsearch.index.mapper.Mapper;
@@ -194,7 +192,7 @@ static class PercolatorFieldType extends MappedFieldType {
194192
RangeFieldMapper.RangeFieldType rangeField;
195193
boolean mapUnmappedFieldsAsText;
196194

197-
PercolatorFieldType(String name, Map<String, String> meta) {
195+
private PercolatorFieldType(String name, Map<String, String> meta) {
198196
super(name, false, false, false, TextSearchInfo.NONE, meta);
199197
}
200198

@@ -203,15 +201,6 @@ public String typeName() {
203201
return CONTENT_TYPE;
204202
}
205203

206-
@Override
207-
public Query existsQuery(QueryShardContext context) {
208-
if (hasDocValues()) {
209-
return new DocValuesFieldExistsQuery(name());
210-
} else {
211-
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
212-
}
213-
}
214-
215204
@Override
216205
public Query termQuery(Object value, QueryShardContext context) {
217206
throw new QueryShardException(context, "Percolator fields are not searchable directly, use a percolate query instead");

0 commit comments

Comments
 (0)