Skip to content

Commit 3834323

Browse files
Fix match_only_text for keyword multi-fields with ignore_above (#131314) (#131333)
1 parent 3c46b2b commit 3834323

File tree

3 files changed

+122
-16
lines changed

3 files changed

+122
-16
lines changed

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.elasticsearch.index.mapper.BlockStoredFieldsReader;
4848
import org.elasticsearch.index.mapper.DocumentParserContext;
4949
import org.elasticsearch.index.mapper.FieldMapper;
50+
import org.elasticsearch.index.mapper.KeywordFieldMapper;
5051
import org.elasticsearch.index.mapper.MappedFieldType;
5152
import org.elasticsearch.index.mapper.MapperBuilderContext;
5253
import org.elasticsearch.index.mapper.SourceValueFetcher;
@@ -66,6 +67,7 @@
6667
import java.io.IOException;
6768
import java.io.UncheckedIOException;
6869
import java.util.ArrayList;
70+
import java.util.Arrays;
6971
import java.util.Collections;
7072
import java.util.List;
7173
import java.util.Map;
@@ -252,6 +254,10 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
252254
String parentField = searchExecutionContext.parentPath(name());
253255
var parent = searchExecutionContext.lookup().fieldType(parentField);
254256
if (parent.isStored()) {
257+
if (parent instanceof KeywordFieldMapper.KeywordFieldType keywordParent
258+
&& keywordParent.ignoreAbove() != Integer.MAX_VALUE) {
259+
return storedFieldFetcher(parentField, keywordParent.originalName());
260+
}
255261
return storedFieldFetcher(parentField);
256262
} else if (parent.hasDocValues()) {
257263
var ifd = searchExecutionContext.getForField(parent, MappedFieldType.FielddataOperation.SEARCH);
@@ -265,7 +271,11 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
265271
if (kwd != null) {
266272
var fieldType = kwd.fieldType();
267273
if (fieldType.isStored()) {
268-
return storedFieldFetcher(fieldType.name());
274+
if (fieldType.ignoreAbove() != Integer.MAX_VALUE) {
275+
return storedFieldFetcher(fieldType.name(), fieldType.originalName());
276+
} else {
277+
return storedFieldFetcher(fieldType.name());
278+
}
269279
} else if (fieldType.hasDocValues()) {
270280
var ifd = searchExecutionContext.getForField(fieldType, MappedFieldType.FielddataOperation.SEARCH);
271281
return docValuesFieldFetcher(ifd);
@@ -312,13 +322,17 @@ private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IO
312322
};
313323
}
314324

315-
private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOException>> storedFieldFetcher(String name) {
316-
var loader = StoredFieldLoader.create(false, Set.of(name));
325+
private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOException>> storedFieldFetcher(String... names) {
326+
var loader = StoredFieldLoader.create(false, Set.of(names));
317327
return context -> {
318328
var leafLoader = loader.getLoader(context, null);
319329
return docId -> {
320330
leafLoader.advanceTo(docId);
321-
return leafLoader.storedFields().get(name);
331+
var storedFields = leafLoader.storedFields();
332+
if (names.length == 1) {
333+
return storedFields.get(names[0]);
334+
}
335+
return Arrays.stream(names).map(storedFields::get).filter(Objects::nonNull).flatMap(List::stream).toList();
322336
};
323337
};
324338
}

modules/mapper-extras/src/yamlRestTest/resources/rest-api-spec/test/match_only_text/10_basic.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,49 @@ synthetic_source match_only_text as multi-field with stored keyword as parent:
479479
hits.hits.0._source.foo: "Apache Lucene powers Elasticsearch"
480480

481481
---
482+
synthetic_source match_only_text as multi-field with ignored stored keyword as parent:
483+
- requires:
484+
cluster_features: [ "mapper.source.mode_from_index_setting" ]
485+
reason: "Source mode configured through index setting"
486+
487+
- do:
488+
indices.create:
489+
index: synthetic_source_test
490+
body:
491+
settings:
492+
index:
493+
mapping.source.mode: synthetic
494+
mappings:
495+
properties:
496+
foo:
497+
type: keyword
498+
store: true
499+
doc_values: false
500+
ignore_above: 10
501+
fields:
502+
text:
503+
type: match_only_text
504+
505+
- do:
506+
index:
507+
index: synthetic_source_test
508+
id: "1"
509+
refresh: true
510+
body:
511+
foo: "Apache Lucene powers Elasticsearch"
512+
513+
- do:
514+
search:
515+
index: synthetic_source_test
516+
body:
517+
query:
518+
match_phrase:
519+
foo.text: apache lucene
520+
521+
- match: { "hits.total.value": 1 }
522+
- match:
523+
hits.hits.0._source.foo: "Apache Lucene powers Elasticsearch"
524+
---
482525
synthetic_source match_only_text with multi-field:
483526
- requires:
484527
cluster_features: [ "mapper.source.mode_from_index_setting" ]
@@ -561,3 +604,47 @@ synthetic_source match_only_text with stored multi-field:
561604
- match: { "hits.total.value": 1 }
562605
- match:
563606
hits.hits.0._source.foo: "Apache Lucene powers Elasticsearch"
607+
608+
---
609+
synthetic_source match_only_text with ignored stored multi-field:
610+
- requires:
611+
cluster_features: [ "mapper.source.mode_from_index_setting" ]
612+
reason: "Source mode configured through index setting"
613+
614+
- do:
615+
indices.create:
616+
index: synthetic_source_test
617+
body:
618+
settings:
619+
index:
620+
mapping.source.mode: synthetic
621+
mappings:
622+
properties:
623+
foo:
624+
type: match_only_text
625+
fields:
626+
raw:
627+
type: keyword
628+
store: true
629+
doc_values: false
630+
ignore_above: 10
631+
632+
- do:
633+
index:
634+
index: synthetic_source_test
635+
id: "1"
636+
refresh: true
637+
body:
638+
foo: "Apache Lucene powers Elasticsearch"
639+
640+
- do:
641+
search:
642+
index: synthetic_source_test
643+
body:
644+
query:
645+
match_phrase:
646+
foo: apache lucene
647+
648+
- match: { "hits.total.value": 1 }
649+
- match:
650+
hits.hits.0._source.foo: "Apache Lucene powers Elasticsearch"

server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ public static final class KeywordFieldType extends StringFieldType {
513513
private final IndexMode indexMode;
514514
private final IndexSortConfig indexSortConfig;
515515
private final boolean hasDocValuesSkipper;
516+
private final String originalName;
516517

517518
public KeywordFieldType(
518519
String name,
@@ -541,6 +542,7 @@ public KeywordFieldType(
541542
this.indexMode = builder.indexMode;
542543
this.indexSortConfig = builder.indexSortConfig;
543544
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
545+
this.originalName = isSyntheticSource ? name + "._original" : null;
544546
}
545547

546548
public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Map<String, String> meta) {
@@ -555,6 +557,7 @@ public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Ma
555557
this.indexMode = IndexMode.STANDARD;
556558
this.indexSortConfig = null;
557559
this.hasDocValuesSkipper = false;
560+
this.originalName = null;
558561
}
559562

560563
public KeywordFieldType(String name) {
@@ -580,6 +583,7 @@ public KeywordFieldType(String name, FieldType fieldType) {
580583
this.indexMode = IndexMode.STANDARD;
581584
this.indexSortConfig = null;
582585
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
586+
this.originalName = null;
583587
}
584588

585589
public KeywordFieldType(String name, NamedAnalyzer analyzer) {
@@ -594,6 +598,7 @@ public KeywordFieldType(String name, NamedAnalyzer analyzer) {
594598
this.indexMode = IndexMode.STANDARD;
595599
this.indexSortConfig = null;
596600
this.hasDocValuesSkipper = false;
601+
this.originalName = null;
597602
}
598603

599604
@Override
@@ -1057,6 +1062,15 @@ public Query automatonQuery(
10571062
) {
10581063
return new AutomatonQueryWithDescription(new Term(name()), automatonSupplier.get(), description);
10591064
}
1065+
1066+
/**
1067+
* The name used to store "original" that have been ignored
1068+
* by {@link KeywordFieldType#ignoreAbove()} so that they can be rebuilt
1069+
* for synthetic source.
1070+
*/
1071+
public String originalName() {
1072+
return originalName;
1073+
}
10601074
}
10611075

10621076
private final boolean indexed;
@@ -1109,7 +1123,7 @@ private KeywordFieldMapper(
11091123
this.useDocValuesSkipper = useDocValuesSkipper;
11101124
this.offsetsFieldName = offsetsFieldName;
11111125
this.indexSourceKeepMode = indexSourceKeepMode;
1112-
this.originalName = isSyntheticSource ? fullPath() + "._original" : null;
1126+
this.originalName = mappedFieldType.originalName();
11131127
}
11141128

11151129
@Override
@@ -1169,7 +1183,7 @@ private boolean indexValue(DocumentParserContext context, XContentString value)
11691183
// Save a copy of the field so synthetic source can load it
11701184
var utfBytes = value.bytes();
11711185
var bytesRef = new BytesRef(utfBytes.bytes(), utfBytes.offset(), utfBytes.length());
1172-
context.doc().add(new StoredField(originalName(), bytesRef));
1186+
context.doc().add(new StoredField(originalName, bytesRef));
11731187
}
11741188
return false;
11751189
}
@@ -1280,15 +1294,6 @@ boolean hasNormalizer() {
12801294
return normalizerName != null;
12811295
}
12821296

1283-
/**
1284-
* The name used to store "original" that have been ignored
1285-
* by {@link KeywordFieldType#ignoreAbove()} so that they can be rebuilt
1286-
* for synthetic source.
1287-
*/
1288-
private String originalName() {
1289-
return originalName;
1290-
}
1291-
12921297
@Override
12931298
protected SyntheticSourceSupport syntheticSourceSupport() {
12941299
if (hasNormalizer()) {
@@ -1337,7 +1342,7 @@ protected BytesRef preserve(BytesRef value) {
13371342
}
13381343

13391344
if (fieldType().ignoreAbove != Integer.MAX_VALUE) {
1340-
layers.add(new CompositeSyntheticFieldLoader.StoredFieldLayer(originalName()) {
1345+
layers.add(new CompositeSyntheticFieldLoader.StoredFieldLayer(originalName) {
13411346
@Override
13421347
protected void writeValue(Object value, XContentBuilder b) throws IOException {
13431348
BytesRef ref = (BytesRef) value;

0 commit comments

Comments
 (0)