Fix index_prefix sub field name on nested text fields (#43862)

This change fixes the name of the index_prefix sub field when the `index_prefix`
option is set on a text field that is nested under an object or a multi-field.
We don't use the full path of the parent field to set the index_prefix field name
so the field is registered under the wrong name. This doesn't break queries since
we always retrieve the prefix field through its parent field but this breaks other
APIs like _field_caps which tries to find the parent of the `index_prefix` field
in the mapping but fails.

Closes #43741
This commit is contained in:
Jim Ferenczi 2019-07-03 09:21:11 +02:00 committed by jimczi
parent 826f38cd70
commit 05c0cff1b6
2 changed files with 83 additions and 12 deletions

View File

@ -111,7 +111,8 @@ public class TextFieldMapper extends FieldMapper {
public static class Builder extends FieldMapper.Builder<Builder, TextFieldMapper> {
private int positionIncrementGap = POSITION_INCREMENT_GAP_USE_ANALYZER;
private PrefixFieldType prefixFieldType;
private int minPrefixChars = -1;
private int maxPrefixChars = -1;
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
@ -162,6 +163,7 @@ public class TextFieldMapper extends FieldMapper {
}
public Builder indexPrefixes(int minChars, int maxChars) {
if (minChars > maxChars) {
throw new IllegalArgumentException("min_chars [" + minChars + "] must be less than max_chars [" + maxChars + "]");
}
@ -171,8 +173,8 @@ public class TextFieldMapper extends FieldMapper {
if (maxChars >= 20) {
throw new IllegalArgumentException("max_chars [" + maxChars + "] must be less than 20");
}
this.prefixFieldType = new PrefixFieldType(name(), name() + "._index_prefix", minChars, maxChars);
fieldType().setPrefixFieldType(this.prefixFieldType);
this.minPrefixChars = minChars;
this.maxPrefixChars = maxChars;
return this;
}
@ -189,7 +191,18 @@ public class TextFieldMapper extends FieldMapper {
}
setupFieldType(context);
PrefixFieldMapper prefixMapper = null;
if (prefixFieldType != null) {
if (minPrefixChars != -1) {
/**
* Mappings before v7.2.1 use {@link Builder#name} instead of {@link Builder#fullName}
* to build prefix field names so we preserve the name that was used at creation time
* even if it is different from the expected one (in case the field is nested under an object
* or a multi-field). This way search will continue to work on old indices and new indices
* will use the expected full name.
**/
String fullName = context.indexCreatedVersion().before(Version.V_7_2_1) ? name() : buildFullName(context);
PrefixFieldType prefixFieldType =
new PrefixFieldType(fullName, fullName + "._index_prefix", minPrefixChars, maxPrefixChars);
fieldType().setPrefixFieldType(prefixFieldType);
if (fieldType().isSearchable() == false) {
throw new IllegalArgumentException("Cannot set index_prefixes on unindexed field [" + name() + "]");
}

View File

@ -22,7 +22,6 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.analysis.MockSynonymAnalyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
@ -636,7 +635,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping));
FieldMapper prefix = (FieldMapper) mapper.mappers().getMapper("field._index_prefix");
FieldType ft = prefix.fieldType;
MappedFieldType ft = prefix.fieldType;
assertEquals(ft.name(), "field._index_prefix");
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS, ft.indexOptions());
}
@ -652,7 +652,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping));
FieldMapper prefix = (FieldMapper) mapper.mappers().getMapper("field._index_prefix");
FieldType ft = prefix.fieldType;
MappedFieldType ft = prefix.fieldType;
assertEquals(ft.name(), "field._index_prefix");
assertEquals(IndexOptions.DOCS, ft.indexOptions());
assertFalse(ft.storeTermVectors());
}
@ -669,11 +670,12 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping));
FieldMapper prefix = (FieldMapper) mapper.mappers().getMapper("field._index_prefix");
FieldType ft = prefix.fieldType;
MappedFieldType ft = prefix.fieldType;
assertEquals(ft.name(), "field._index_prefix");
if (indexService.getIndexSettings().getIndexVersionCreated().onOrAfter(Version.V_6_4_0)) {
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, ft.indexOptions());
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, ft.indexOptions());
} else {
assertEquals(IndexOptions.DOCS, ft.indexOptions());
assertEquals(IndexOptions.DOCS, ft.indexOptions());
}
assertFalse(ft.storeTermVectors());
}
@ -690,7 +692,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping));
FieldMapper prefix = (FieldMapper) mapper.mappers().getMapper("field._index_prefix");
FieldType ft = prefix.fieldType;
MappedFieldType ft = prefix.fieldType;
assertEquals(ft.name(), "field._index_prefix");
if (indexService.getIndexSettings().getIndexVersionCreated().onOrAfter(Version.V_6_4_0)) {
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, ft.indexOptions());
} else {
@ -711,7 +714,8 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping));
FieldMapper prefix = (FieldMapper) mapper.mappers().getMapper("field._index_prefix");
FieldType ft = prefix.fieldType;
MappedFieldType ft = prefix.fieldType;
assertEquals(ft.name(), "field._index_prefix");
if (indexService.getIndexSettings().getIndexVersionCreated().onOrAfter(Version.V_6_4_0)) {
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, ft.indexOptions());
} else {
@ -721,6 +725,60 @@ public class TextFieldMapperTests extends ESSingleNodeTestCase {
}
}
public void testNestedIndexPrefixes() throws IOException {
{
String mapping = Strings.toString(XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("object")
.field("type", "object")
.startObject("properties")
.startObject("field")
.field("type", "text")
.startObject("index_prefixes").endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.endObject());
indexService.mapperService().merge("type", new CompressedXContent(mapping), MergeReason.MAPPING_UPDATE);
MappedFieldType textField = indexService.mapperService().fullName("object.field");
assertNotNull(textField);
assertThat(textField, instanceOf(TextFieldType.class));
MappedFieldType prefix = ((TextFieldType) textField).getPrefixFieldType();
assertEquals(prefix.name(), "object.field._index_prefix");
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, prefix.indexOptions());
assertFalse(prefix.storeTermVectorOffsets());
}
{
String mapping = Strings.toString(XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("body")
.field("type", "text")
.startObject("fields")
.startObject("with_prefix")
.field("type", "text")
.startObject("index_prefixes").endObject()
.endObject()
.endObject()
.endObject()
.endObject()
.endObject());
indexService.mapperService().merge("type", new CompressedXContent(mapping), MergeReason.MAPPING_UPDATE);
MappedFieldType textField = indexService.mapperService().fullName("body.with_prefix");
assertNotNull(textField);
assertThat(textField, instanceOf(TextFieldType.class));
MappedFieldType prefix = ((TextFieldType) textField).getPrefixFieldType();
assertEquals(prefix.name(), "body.with_prefix._index_prefix");
assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS, prefix.indexOptions());
assertFalse(prefix.storeTermVectorOffsets());
}
}
public void testFastPhraseMapping() throws IOException {
QueryShardContext queryShardContext = indexService.newQueryShardContext(