Skip to content

Commit 257cdcd

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
1 parent 261e21a commit 257cdcd

File tree

2 files changed

+167
-56
lines changed

2 files changed

+167
-56
lines changed

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

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package org.elasticsearch.index.mapper.internal;
2121

2222
import com.google.common.collect.UnmodifiableIterator;
23-
2423
import org.apache.lucene.document.Field;
2524
import org.apache.lucene.document.FieldType;
2625
import org.apache.lucene.document.SortedSetDocValuesField;
@@ -30,14 +29,16 @@
3029
import org.elasticsearch.ElasticsearchIllegalArgumentException;
3130
import org.elasticsearch.Version;
3231
import org.elasticsearch.common.Nullable;
32+
import org.elasticsearch.common.Strings;
3333
import org.elasticsearch.common.lucene.Lucene;
3434
import org.elasticsearch.common.settings.Settings;
3535
import org.elasticsearch.common.xcontent.XContentBuilder;
36-
import org.elasticsearch.common.xcontent.XContentFactory;
3736
import org.elasticsearch.index.fielddata.FieldDataType;
3837
import org.elasticsearch.index.mapper.InternalMapper;
3938
import org.elasticsearch.index.mapper.Mapper;
4039
import org.elasticsearch.index.mapper.MapperParsingException;
40+
import org.elasticsearch.index.mapper.MergeContext;
41+
import org.elasticsearch.index.mapper.MergeMappingException;
4142
import org.elasticsearch.index.mapper.ParseContext;
4243
import org.elasticsearch.index.mapper.RootMapper;
4344
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
@@ -48,6 +49,7 @@
4849
import java.util.List;
4950
import java.util.Map;
5051

52+
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue;
5153
import static org.elasticsearch.index.mapper.MapperBuilders.fieldNames;
5254
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
5355

@@ -65,79 +67,83 @@ public class FieldNamesFieldMapper extends AbstractFieldMapper<String> implement
6567

6668
public static class Defaults extends AbstractFieldMapper.Defaults {
6769
public static final String NAME = FieldNamesFieldMapper.NAME;
68-
public static final String INDEX_NAME = FieldNamesFieldMapper.NAME;
69-
70+
71+
public static final EnabledAttributeMapper ENABLED_STATE = EnabledAttributeMapper.UNSET_ENABLED;
7072
public static final FieldType FIELD_TYPE = new FieldType(AbstractFieldMapper.Defaults.FIELD_TYPE);
71-
// TODO: this field should be removed?
72-
public static final FieldType FIELD_TYPE_PRE_1_3_0;
7373

7474
static {
7575
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
7676
FIELD_TYPE.setTokenized(false);
7777
FIELD_TYPE.setStored(false);
7878
FIELD_TYPE.setOmitNorms(true);
7979
FIELD_TYPE.freeze();
80-
FIELD_TYPE_PRE_1_3_0 = new FieldType(FIELD_TYPE);
81-
FIELD_TYPE_PRE_1_3_0.setIndexOptions(IndexOptions.NONE);
82-
FIELD_TYPE_PRE_1_3_0.freeze();
8380
}
8481
}
8582

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

9086
public Builder() {
9187
super(Defaults.NAME, new FieldType(Defaults.FIELD_TYPE));
92-
indexName = Defaults.INDEX_NAME;
88+
indexName = Defaults.NAME;
9389
}
9490

9591
@Override
9692
public Builder index(boolean index) {
97-
indexIsExplicit = true;
93+
enabled(index);
9894
return super.index(index);
9995
}
96+
97+
public Builder enabled(boolean enabled) {
98+
this.enabledState = enabled ? EnabledAttributeMapper.ENABLED : EnabledAttributeMapper.DISABLED;
99+
return this;
100+
}
100101

101102
@Override
102103
public FieldNamesFieldMapper build(BuilderContext context) {
103-
if ((context.indexCreatedVersion() == null || context.indexCreatedVersion().before(Version.V_1_3_0)) && !indexIsExplicit) {
104-
fieldType.setIndexOptions(IndexOptions.NONE);
105-
}
106-
return new FieldNamesFieldMapper(name, indexName, boost, fieldType, fieldDataSettings, context.indexSettings());
104+
return new FieldNamesFieldMapper(name, indexName, boost, fieldType, enabledState, fieldDataSettings, context.indexSettings());
107105
}
108106
}
109107

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

123131
private final FieldType defaultFieldType;
124-
125-
private static FieldType defaultFieldType(Settings indexSettings) {
126-
return indexSettings != null && Version.indexCreated(indexSettings).onOrAfter(Version.V_1_3_0) ? Defaults.FIELD_TYPE : Defaults.FIELD_TYPE_PRE_1_3_0;
127-
}
132+
private EnabledAttributeMapper enabledState;
128133

129134
public FieldNamesFieldMapper(Settings indexSettings) {
130-
this(Defaults.NAME, Defaults.INDEX_NAME, indexSettings);
131-
}
132-
133-
protected FieldNamesFieldMapper(String name, String indexName, Settings indexSettings) {
134-
this(name, indexName, Defaults.BOOST, new FieldType(defaultFieldType(indexSettings)), null, indexSettings);
135+
this(Defaults.NAME, Defaults.NAME, Defaults.BOOST, new FieldType(Defaults.FIELD_TYPE), Defaults.ENABLED_STATE, null, indexSettings);
135136
}
136137

137-
public FieldNamesFieldMapper(String name, String indexName, float boost, FieldType fieldType, @Nullable Settings fieldDataSettings, Settings indexSettings) {
138+
public FieldNamesFieldMapper(String name, String indexName, float boost, FieldType fieldType, EnabledAttributeMapper enabledState, @Nullable Settings fieldDataSettings, Settings indexSettings) {
138139
super(new Names(name, indexName, indexName, name), boost, fieldType, null, Lucene.KEYWORD_ANALYZER,
139140
Lucene.KEYWORD_ANALYZER, null, null, fieldDataSettings, indexSettings);
140-
this.defaultFieldType = defaultFieldType(indexSettings);
141+
this.defaultFieldType = Defaults.FIELD_TYPE;
142+
this.enabledState = enabledState;
143+
}
144+
145+
public boolean enabled() {
146+
return enabledState.enabled;
141147
}
142148

143149
@Override
@@ -216,7 +222,7 @@ public String next() {
216222

217223
@Override
218224
protected void parseCreateField(ParseContext context, List<Field> fields) throws IOException {
219-
if (fieldType.indexOptions() == IndexOptions.NONE && !fieldType.stored() && !hasDocValues()) {
225+
if (enabledState.enabled == false) {
220226
return;
221227
}
222228
for (ParseContext.Document document : context.docs()) {
@@ -244,12 +250,32 @@ protected String contentType() {
244250

245251
@Override
246252
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
247-
XContentBuilder json = XContentFactory.jsonBuilder();
248-
super.toXContent(json, params);
249-
if (json.string().equals("\"" + NAME + "\"{\"type\":\"" + CONTENT_TYPE + "\"}")) {
253+
boolean includeDefaults = params.paramAsBoolean("include_defaults", false);
254+
255+
if (includeDefaults == false && fieldType().equals(Defaults.FIELD_TYPE) && enabledState == Defaults.ENABLED_STATE) {
250256
return builder;
251257
}
252-
return super.toXContent(builder, params);
258+
259+
builder.startObject(NAME);
260+
if (includeDefaults || enabledState != Defaults.ENABLED_STATE) {
261+
builder.field("enabled", enabledState.enabled);
262+
}
263+
if (includeDefaults || fieldType().equals(Defaults.FIELD_TYPE) == false) {
264+
super.doXContentBody(builder, includeDefaults, params);
265+
}
266+
267+
builder.endObject();
268+
return builder;
269+
}
270+
271+
@Override
272+
public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
273+
FieldNamesFieldMapper fieldNamesMapperMergeWith = (FieldNamesFieldMapper)mergeWith;
274+
if (!mergeContext.mergeFlags().simulate()) {
275+
if (fieldNamesMapperMergeWith.enabledState != enabledState && !fieldNamesMapperMergeWith.enabledState.unset()) {
276+
this.enabledState = fieldNamesMapperMergeWith.enabledState;
277+
}
278+
}
253279
}
254280

255281
@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)