Skip to content

Commit f1a5fdf

Browse files
committed
Mappings: Add enabled flag for _field_names to replace disabling through index=no
Almost all of our meta fields that allow enabling/disabling have an `enabled` setting. However, _field_names is enabled by default, and disabling requires setting `index=no`. This change adds a flag similar to that with other meta fields. closes #9893 Conflicts: src/main/java/org/elasticsearch/index/mapper/internal/FieldNamesFieldMapper.java
1 parent 19e30cc commit f1a5fdf

File tree

2 files changed

+173
-55
lines changed

2 files changed

+173
-55
lines changed

src/main/java/org/elasticsearch/index/mapper/internal/FieldNamesFieldMapper.java

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,21 @@
2929
import org.elasticsearch.ElasticsearchIllegalArgumentException;
3030
import org.elasticsearch.Version;
3131
import org.elasticsearch.common.Nullable;
32+
import org.elasticsearch.common.Strings;
3233
import org.elasticsearch.common.lucene.Lucene;
3334
import org.elasticsearch.common.settings.Settings;
3435
import org.elasticsearch.common.xcontent.XContentBuilder;
35-
import org.elasticsearch.common.xcontent.XContentFactory;
3636
import org.elasticsearch.index.codec.docvaluesformat.DocValuesFormatProvider;
3737
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
3838
import org.elasticsearch.index.fielddata.FieldDataType;
39-
import org.elasticsearch.index.mapper.*;
39+
import org.elasticsearch.index.mapper.InternalMapper;
40+
import org.elasticsearch.index.mapper.Mapper;
41+
import org.elasticsearch.index.mapper.MapperParsingException;
42+
import org.elasticsearch.index.mapper.MergeContext;
43+
import org.elasticsearch.index.mapper.MergeMappingException;
44+
import org.elasticsearch.index.mapper.ParseContext;
45+
import org.elasticsearch.index.mapper.RootMapper;
46+
4047
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
4148

4249
import java.io.IOException;
@@ -45,6 +52,7 @@
4552
import java.util.List;
4653
import java.util.Map;
4754

55+
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue;
4856
import static org.elasticsearch.index.mapper.MapperBuilders.fieldNames;
4957
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
5058

@@ -62,10 +70,9 @@ public class FieldNamesFieldMapper extends AbstractFieldMapper<String> implement
6270

6371
public static class Defaults extends AbstractFieldMapper.Defaults {
6472
public static final String NAME = FieldNamesFieldMapper.NAME;
65-
public static final String INDEX_NAME = FieldNamesFieldMapper.NAME;
66-
73+
74+
public static final EnabledAttributeMapper ENABLED_STATE = EnabledAttributeMapper.UNSET_ENABLED;
6775
public static final FieldType FIELD_TYPE = new FieldType(AbstractFieldMapper.Defaults.FIELD_TYPE);
68-
public static final FieldType FIELD_TYPE_PRE_1_3_0;
6976

7077
static {
7178
FIELD_TYPE.setIndexed(true);
@@ -74,68 +81,74 @@ public static class Defaults extends AbstractFieldMapper.Defaults {
7481
FIELD_TYPE.setOmitNorms(true);
7582
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS_ONLY);
7683
FIELD_TYPE.freeze();
77-
FIELD_TYPE_PRE_1_3_0 = new FieldType(FIELD_TYPE);
78-
FIELD_TYPE_PRE_1_3_0.setIndexed(false);
79-
FIELD_TYPE_PRE_1_3_0.freeze();
8084
}
8185
}
8286

8387
public static class Builder extends AbstractFieldMapper.Builder<Builder, FieldNamesFieldMapper> {
84-
85-
private boolean indexIsExplicit;
88+
private EnabledAttributeMapper enabledState = Defaults.ENABLED_STATE;
8689

8790
public Builder() {
8891
super(Defaults.NAME, new FieldType(Defaults.FIELD_TYPE));
89-
indexName = Defaults.INDEX_NAME;
92+
indexName = Defaults.NAME;
9093
}
9194

9295
@Override
9396
public Builder index(boolean index) {
94-
indexIsExplicit = true;
97+
enabled(index);
9598
return super.index(index);
9699
}
100+
101+
public Builder enabled(boolean enabled) {
102+
this.enabledState = enabled ? EnabledAttributeMapper.ENABLED : EnabledAttributeMapper.DISABLED;
103+
return this;
104+
}
97105

98106
@Override
99107
public FieldNamesFieldMapper build(BuilderContext context) {
100-
if ((context.indexCreatedVersion() == null || context.indexCreatedVersion().before(Version.V_1_3_0)) && !indexIsExplicit) {
101-
fieldType.setIndexed(false);
102-
}
103-
return new FieldNamesFieldMapper(name, indexName, boost, fieldType, postingsProvider, docValuesProvider, fieldDataSettings, context.indexSettings());
108+
return new FieldNamesFieldMapper(name, indexName, boost, fieldType, postingsProvider, docValuesProvider, enabledState, fieldDataSettings, context.indexSettings());
104109
}
105110
}
106111

107112
public static class TypeParser implements Mapper.TypeParser {
108113
@Override
109114
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
110-
if (parserContext.indexVersionCreated().onOrAfter(Version.V_1_3_0)) {
111-
FieldNamesFieldMapper.Builder builder = fieldNames();
112-
parseField(builder, builder.name, node, parserContext);
113-
return builder;
114-
} else {
115-
throw new ElasticsearchIllegalArgumentException("type="+CONTENT_TYPE+" is not supported on indices created before version 1.3.0 is your cluster running multiple datanode versions?");
115+
if (parserContext.indexVersionCreated().before(Version.V_1_3_0)) {
116+
throw new ElasticsearchIllegalArgumentException("type="+CONTENT_TYPE+" is not supported on indices created before version 1.3.0. Is your cluster running multiple datanode versions?");
116117
}
118+
119+
FieldNamesFieldMapper.Builder builder = fieldNames();
120+
parseField(builder, builder.name, node, parserContext);
121+
122+
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
123+
Map.Entry<String, Object> entry = iterator.next();
124+
String fieldName = Strings.toUnderscoreCase(entry.getKey());
125+
Object fieldNode = entry.getValue();
126+
if (fieldName.equals("enabled")) {
127+
builder.enabled(nodeBooleanValue(fieldNode));
128+
iterator.remove();
129+
}
130+
}
131+
return builder;
117132
}
118133
}
119134

120135
private final FieldType defaultFieldType;
121-
122-
private static FieldType defaultFieldType(Settings indexSettings) {
123-
return indexSettings != null && Version.indexCreated(indexSettings).onOrAfter(Version.V_1_3_0) ? Defaults.FIELD_TYPE : Defaults.FIELD_TYPE_PRE_1_3_0;
124-
}
136+
private EnabledAttributeMapper enabledState;
125137

126138
public FieldNamesFieldMapper(Settings indexSettings) {
127-
this(Defaults.NAME, Defaults.INDEX_NAME, indexSettings);
128-
}
129-
130-
protected FieldNamesFieldMapper(String name, String indexName, Settings indexSettings) {
131-
this(name, indexName, Defaults.BOOST, new FieldType(defaultFieldType(indexSettings)), null, null, null, indexSettings);
139+
this(Defaults.NAME, Defaults.NAME, Defaults.BOOST, new FieldType(Defaults.FIELD_TYPE), null, null, Defaults.ENABLED_STATE, null, indexSettings);
132140
}
133141

134142
public FieldNamesFieldMapper(String name, String indexName, float boost, FieldType fieldType, PostingsFormatProvider postingsProvider,
135-
DocValuesFormatProvider docValuesProvider, @Nullable Settings fieldDataSettings, Settings indexSettings) {
143+
DocValuesFormatProvider docValuesProvider, EnabledAttributeMapper enabledState, @Nullable Settings fieldDataSettings, Settings indexSettings) {
136144
super(new Names(name, indexName, indexName, name), boost, fieldType, null, Lucene.KEYWORD_ANALYZER,
137145
Lucene.KEYWORD_ANALYZER, postingsProvider, docValuesProvider, null, null, fieldDataSettings, indexSettings);
138-
this.defaultFieldType = defaultFieldType(indexSettings);
146+
this.defaultFieldType = Defaults.FIELD_TYPE;
147+
this.enabledState = enabledState;
148+
}
149+
150+
public boolean enabled() {
151+
return enabledState.enabled;
139152
}
140153

141154
@Override
@@ -214,7 +227,7 @@ public String next() {
214227

215228
@Override
216229
protected void parseCreateField(ParseContext context, List<Field> fields) throws IOException {
217-
if (!fieldType.indexed() && !fieldType.stored() && !hasDocValues()) {
230+
if (enabledState.enabled == false) {
218231
return;
219232
}
220233
for (ParseContext.Document document : context.docs()) {
@@ -242,12 +255,32 @@ protected String contentType() {
242255

243256
@Override
244257
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
245-
XContentBuilder json = XContentFactory.jsonBuilder();
246-
super.toXContent(json, params);
247-
if (json.string().equals("\"" + NAME + "\"{\"type\":\"" + CONTENT_TYPE + "\"}")) {
258+
boolean includeDefaults = params.paramAsBoolean("include_defaults", false);
259+
260+
if (includeDefaults == false && fieldType().equals(Defaults.FIELD_TYPE) && enabledState == Defaults.ENABLED_STATE) {
248261
return builder;
249262
}
250-
return super.toXContent(builder, params);
263+
264+
builder.startObject(NAME);
265+
if (includeDefaults || enabledState != Defaults.ENABLED_STATE) {
266+
builder.field("enabled", enabledState.enabled);
267+
}
268+
if (includeDefaults || fieldType().equals(Defaults.FIELD_TYPE) == false) {
269+
super.doXContentBody(builder, includeDefaults, params);
270+
}
271+
272+
builder.endObject();
273+
return builder;
274+
}
275+
276+
@Override
277+
public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
278+
FieldNamesFieldMapper fieldNamesMapperMergeWith = (FieldNamesFieldMapper)mergeWith;
279+
if (!mergeContext.mergeFlags().simulate()) {
280+
if (fieldNamesMapperMergeWith.enabledState != enabledState && !fieldNamesMapperMergeWith.enabledState.unset()) {
281+
this.enabledState = fieldNamesMapperMergeWith.enabledState;
282+
}
283+
}
251284
}
252285

253286
@Override

src/test/java/org/elasticsearch/index/mapper/internal/FieldNamesFieldMapperTests.java

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,33 @@
1919

2020
package org.elasticsearch.index.mapper.internal;
2121

22-
import com.google.common.collect.ImmutableSet;
23-
import org.apache.lucene.index.IndexableField;
2422
import org.elasticsearch.common.xcontent.XContentFactory;
2523
import org.elasticsearch.index.mapper.DocumentMapper;
24+
import org.elasticsearch.index.mapper.DocumentMapperParser;
2625
import org.elasticsearch.index.mapper.ParsedDocument;
2726
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
2827

2928
import java.util.Arrays;
30-
import java.util.HashSet;
31-
import java.util.Set;
29+
import java.util.SortedSet;
30+
import java.util.TreeSet;
3231

3332
public class FieldNamesFieldMapperTests extends ElasticsearchSingleNodeTest {
3433

35-
private static Set<String> extract(String path) {
36-
return ImmutableSet.<String>builder().addAll(FieldNamesFieldMapper.extractFieldNames(path)).build();
34+
private static SortedSet<String> extract(String path) {
35+
SortedSet<String> set = new TreeSet<>();
36+
for (String fieldName : FieldNamesFieldMapper.extractFieldNames(path)) {
37+
set.add(fieldName);
38+
}
39+
return set;
40+
}
41+
42+
private static <T> SortedSet<T> set(T... values) {
43+
return new TreeSet<>(Arrays.asList(values));
3744
}
3845

39-
private static <T> Set<T> set(T... values) {
40-
return new HashSet<T>(Arrays.asList(values));
46+
void assertFieldNames(SortedSet<String> expected, ParsedDocument doc) {
47+
String[] got = doc.rootDoc().getValues("_field_names");
48+
assertEquals(expected, set(got));
4149
}
4250

4351
public void testExtractFieldNames() {
@@ -50,8 +58,9 @@ public void testExtractFieldNames() {
5058
assertEquals(set("", ".", ".."), extract(".."));
5159
}
5260

53-
public void test() throws Exception {
54-
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse(XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string());
61+
public void testInjectIntoDocDuringParsing() throws Exception {
62+
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string();
63+
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
5564

5665
ParsedDocument doc = defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
5766
.startObject()
@@ -61,13 +70,89 @@ public void test() throws Exception {
6170
.endObject()
6271
.endObject()
6372
.bytes());
73+
74+
assertFieldNames(set("a", "b", "b.c", "_uid", "_type", "_version", "_source", "_all"), doc);
75+
}
76+
77+
public void testExplicitEnabled() throws Exception {
78+
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
79+
.startObject("_field_names").field("enabled", true).endObject()
80+
.endObject().endObject().string();
81+
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
82+
FieldNamesFieldMapper fieldNamesMapper = docMapper.rootMapper(FieldNamesFieldMapper.class);
83+
assertTrue(fieldNamesMapper.enabled());
6484

65-
final Set<String> fieldNames = new HashSet<>();
66-
for (IndexableField field : doc.rootDoc().getFields()) {
67-
if (FieldNamesFieldMapper.CONTENT_TYPE.equals(field.name())) {
68-
fieldNames.add(field.stringValue());
69-
}
70-
}
71-
assertEquals(new HashSet<>(Arrays.asList("a", "b", "b.c", "_uid", "_type", "_version", "_source", "_all")), fieldNames);
85+
ParsedDocument doc = docMapper.parse("type", "1", XContentFactory.jsonBuilder()
86+
.startObject()
87+
.field("field", "value")
88+
.endObject()
89+
.bytes());
90+
91+
assertFieldNames(set("field", "_uid", "_type", "_version", "_source", "_all"), doc);
92+
}
93+
94+
public void testDisabled() throws Exception {
95+
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
96+
.startObject("_field_names").field("enabled", false).endObject()
97+
.endObject().endObject().string();
98+
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
99+
FieldNamesFieldMapper fieldNamesMapper = docMapper.rootMapper(FieldNamesFieldMapper.class);
100+
assertFalse(fieldNamesMapper.enabled());
101+
102+
ParsedDocument doc = docMapper.parse("type", "1", XContentFactory.jsonBuilder()
103+
.startObject()
104+
.field("field", "value")
105+
.endObject()
106+
.bytes());
107+
108+
assertNull(doc.rootDoc().get("_field_names"));
109+
}
110+
111+
public void testDisablingBackcompat() throws Exception {
112+
// before 1.5, disabling happened by setting index:no
113+
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
114+
.startObject("_field_names").field("index", "no").endObject()
115+
.endObject().endObject().string();
116+
117+
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
118+
FieldNamesFieldMapper fieldNamesMapper = docMapper.rootMapper(FieldNamesFieldMapper.class);
119+
assertFalse(fieldNamesMapper.enabled());
120+
121+
ParsedDocument doc = docMapper.parse("type", "1", XContentFactory.jsonBuilder()
122+
.startObject()
123+
.field("field", "value")
124+
.endObject()
125+
.bytes());
126+
127+
assertNull(doc.rootDoc().get("_field_names"));
128+
}
129+
130+
public void testFieldTypeSettings() throws Exception {
131+
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
132+
.startObject("_field_names").field("store", "yes").endObject()
133+
.endObject().endObject().string();
134+
135+
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
136+
FieldNamesFieldMapper fieldNamesMapper = docMapper.rootMapper(FieldNamesFieldMapper.class);
137+
assertTrue(fieldNamesMapper.fieldType().stored());
138+
}
139+
140+
public void testMergingMappings() throws Exception {
141+
String enabledMapping = XContentFactory.jsonBuilder().startObject().startObject("type")
142+
.startObject("_field_names").field("enabled", true).endObject()
143+
.endObject().endObject().string();
144+
String disabledMapping = XContentFactory.jsonBuilder().startObject().startObject("type")
145+
.startObject("_field_names").field("enabled", false).endObject()
146+
.endObject().endObject().string();
147+
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
148+
149+
DocumentMapper mapperEnabled = parser.parse(enabledMapping);
150+
DocumentMapper mapperDisabled = parser.parse(disabledMapping);
151+
mapperEnabled.merge(mapperDisabled, DocumentMapper.MergeFlags.mergeFlags().simulate(false));
152+
assertFalse(mapperEnabled.rootMapper(FieldNamesFieldMapper.class).enabled());
153+
154+
mapperEnabled = parser.parse(enabledMapping);
155+
mapperDisabled.merge(mapperEnabled, DocumentMapper.MergeFlags.mergeFlags().simulate(false));
156+
assertTrue(mapperEnabled.rootMapper(FieldNamesFieldMapper.class).enabled());
72157
}
73158
}

0 commit comments

Comments
 (0)