Skip to content

Commit 4e74288

Browse files
Fix match_only_text for keyword multi-fields with ignore_above (#131314) (#131338)
(cherry picked from commit 6f8be9c) # Conflicts: # server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java
1 parent d7b6aab commit 4e74288

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;
@@ -246,6 +248,10 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
246248
String parentField = searchExecutionContext.parentPath(name());
247249
var parent = searchExecutionContext.lookup().fieldType(parentField);
248250
if (parent.isStored()) {
251+
if (parent instanceof KeywordFieldMapper.KeywordFieldType keywordParent
252+
&& keywordParent.ignoreAbove() != Integer.MAX_VALUE) {
253+
return storedFieldFetcher(parentField, keywordParent.originalName());
254+
}
249255
return storedFieldFetcher(parentField);
250256
} else if (parent.hasDocValues()) {
251257
var ifd = searchExecutionContext.getForField(parent, MappedFieldType.FielddataOperation.SEARCH);
@@ -259,7 +265,11 @@ private IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOExcepti
259265
if (kwd != null) {
260266
var fieldType = kwd.fieldType();
261267
if (fieldType.isStored()) {
262-
return storedFieldFetcher(fieldType.name());
268+
if (fieldType.ignoreAbove() != Integer.MAX_VALUE) {
269+
return storedFieldFetcher(fieldType.name(), fieldType.originalName());
270+
} else {
271+
return storedFieldFetcher(fieldType.name());
272+
}
263273
} else if (fieldType.hasDocValues()) {
264274
var ifd = searchExecutionContext.getForField(fieldType, MappedFieldType.FielddataOperation.SEARCH);
265275
return docValuesFieldFetcher(ifd);
@@ -306,13 +316,17 @@ private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IO
306316
};
307317
}
308318

309-
private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOException>> storedFieldFetcher(String name) {
310-
var loader = StoredFieldLoader.create(false, Set.of(name));
319+
private static IOFunction<LeafReaderContext, CheckedIntFunction<List<Object>, IOException>> storedFieldFetcher(String... names) {
320+
var loader = StoredFieldLoader.create(false, Set.of(names));
311321
return context -> {
312322
var leafLoader = loader.getLoader(context, null);
313323
return docId -> {
314324
leafLoader.advanceTo(docId);
315-
return leafLoader.storedFields().get(name);
325+
var storedFields = leafLoader.storedFields();
326+
if (names.length == 1) {
327+
return storedFields.get(names[0]);
328+
}
329+
return Arrays.stream(names).map(storedFields::get).filter(Objects::nonNull).flatMap(List::stream).toList();
316330
};
317331
};
318332
}

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
@@ -425,6 +425,7 @@ public static final class KeywordFieldType extends StringFieldType {
425425
private final FieldValues<String> scriptValues;
426426
private final boolean isDimension;
427427
private final boolean isSyntheticSource;
428+
private final String originalName;
428429

429430
public KeywordFieldType(
430431
String name,
@@ -450,6 +451,7 @@ public KeywordFieldType(
450451
this.scriptValues = builder.scriptValues();
451452
this.isDimension = builder.dimension.getValue();
452453
this.isSyntheticSource = isSyntheticSource;
454+
this.originalName = isSyntheticSource ? name + "._original" : null;
453455
}
454456

455457
public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Map<String, String> meta) {
@@ -461,6 +463,7 @@ public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Ma
461463
this.scriptValues = null;
462464
this.isDimension = false;
463465
this.isSyntheticSource = false;
466+
this.originalName = null;
464467
}
465468

466469
public KeywordFieldType(String name) {
@@ -483,6 +486,7 @@ public KeywordFieldType(String name, FieldType fieldType) {
483486
this.scriptValues = null;
484487
this.isDimension = false;
485488
this.isSyntheticSource = false;
489+
this.originalName = null;
486490
}
487491

488492
public KeywordFieldType(String name, NamedAnalyzer analyzer) {
@@ -494,6 +498,7 @@ public KeywordFieldType(String name, NamedAnalyzer analyzer) {
494498
this.scriptValues = null;
495499
this.isDimension = false;
496500
this.isSyntheticSource = false;
501+
this.originalName = null;
497502
}
498503

499504
@Override
@@ -945,6 +950,15 @@ public Query automatonQuery(
945950
) {
946951
return new AutomatonQueryWithDescription(new Term(name()), automatonSupplier.get(), description);
947952
}
953+
954+
/**
955+
* The name used to store "original" that have been ignored
956+
* by {@link KeywordFieldType#ignoreAbove()} so that they can be rebuilt
957+
* for synthetic source.
958+
*/
959+
public String originalName() {
960+
return originalName;
961+
}
948962
}
949963

950964
private final boolean indexed;
@@ -992,7 +1006,7 @@ private KeywordFieldMapper(
9921006
this.ignoreAbove = builder.ignoreAbove.getValue();
9931007
this.offsetsFieldName = offsetsFieldName;
9941008
this.indexSourceKeepMode = indexSourceKeepMode;
995-
this.originalName = isSyntheticSource ? fullPath() + "._original" : null;
1009+
this.originalName = mappedFieldType.originalName();
9961010
}
9971011

9981012
@Override
@@ -1052,7 +1066,7 @@ private boolean indexValue(DocumentParserContext context, XContentString value)
10521066
// Save a copy of the field so synthetic source can load it
10531067
var utfBytes = value.bytes();
10541068
var bytesRef = new BytesRef(utfBytes.bytes(), utfBytes.offset(), utfBytes.length());
1055-
context.doc().add(new StoredField(originalName(), bytesRef));
1069+
context.doc().add(new StoredField(originalName, bytesRef));
10561070
}
10571071
return false;
10581072
}
@@ -1155,15 +1169,6 @@ boolean hasNormalizer() {
11551169
return normalizerName != null;
11561170
}
11571171

1158-
/**
1159-
* The name used to store "original" that have been ignored
1160-
* by {@link KeywordFieldType#ignoreAbove()} so that they can be rebuilt
1161-
* for synthetic source.
1162-
*/
1163-
private String originalName() {
1164-
return originalName;
1165-
}
1166-
11671172
@Override
11681173
protected SyntheticSourceSupport syntheticSourceSupport() {
11691174
if (hasNormalizer()) {
@@ -1212,7 +1217,7 @@ protected BytesRef preserve(BytesRef value) {
12121217
}
12131218

12141219
if (fieldType().ignoreAbove != Integer.MAX_VALUE) {
1215-
layers.add(new CompositeSyntheticFieldLoader.StoredFieldLayer(originalName()) {
1220+
layers.add(new CompositeSyntheticFieldLoader.StoredFieldLayer(originalName) {
12161221
@Override
12171222
protected void writeValue(Object value, XContentBuilder b) throws IOException {
12181223
BytesRef ref = (BytesRef) value;

0 commit comments

Comments
 (0)