Skip to content

Commit 232f494

Browse files
authored
Pull the flattened field type into a mapper plugin. (#43250)
This PR pulls `FlatObjectFieldMapper` into its own `MapperPlugin`. To do so it introduces a new interface `DynamicKeyFieldMapper` with the method `keyedFieldType(String key)`, which gives the opportunity to return a special field type for a subfield.
1 parent c995f68 commit 232f494

File tree

44 files changed

+1188
-903
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1188
-903
lines changed

modules/mapper-flattened/build.gradle

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
esplugin {
21+
description 'Module for the flattened field type, which allows JSON objects to be flattened into a single field.'
22+
classname 'org.elasticsearch.plugin.mapper.FlattenedMapperPlugin'
23+
}

server/src/main/java/org/elasticsearch/index/mapper/FlatObjectFieldMapper.java renamed to modules/mapper-flattened/src/main/java/org/elasticsearch/index/mapper/FlatObjectFieldMapper.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
* "key\0some value" and "key2.key3\0true". Note that \0 is used as a reserved separator
8989
* character (see {@link FlatObjectFieldParser#SEPARATOR}).
9090
*/
91-
public final class FlatObjectFieldMapper extends FieldMapper {
91+
public final class FlatObjectFieldMapper extends DynamicKeyFieldMapper {
9292

9393
public static final String CONTENT_TYPE = "flattened";
9494
private static final String KEYED_FIELD_SUFFIX = "._keyed";
@@ -160,7 +160,7 @@ public Builder splitQueriesOnWhitespace(boolean splitQueriesOnWhitespace) {
160160
}
161161

162162
@Override
163-
public Builder addMultiField(Mapper.Builder mapperBuilder) {
163+
public Builder addMultiField(Mapper.Builder<?, ?> mapperBuilder) {
164164
throw new UnsupportedOperationException("[fields] is not supported for [" + CONTENT_TYPE + "] fields.");
165165
}
166166

@@ -189,7 +189,7 @@ public FlatObjectFieldMapper build(BuilderContext context) {
189189
public static class TypeParser implements Mapper.TypeParser {
190190
@Override
191191
public Mapper.Builder<?,?> parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
192-
FlatObjectFieldMapper.Builder builder = new FlatObjectFieldMapper.Builder(name);
192+
Builder builder = new Builder(name);
193193
parseField(builder, name, node, parserContext);
194194
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
195195
Map.Entry<String, Object> entry = iterator.next();
@@ -424,6 +424,11 @@ public OrdinalMap getOrdinalMap() {
424424
+ delegate.getFieldName() + "] does not allow access to the underlying ordinal map.");
425425
}
426426

427+
@Override
428+
public boolean supportsGlobalOrdinalsMapping() {
429+
return false;
430+
}
431+
427432
@Override
428433
public Index index() {
429434
return delegate.index();
@@ -534,7 +539,7 @@ private FlatObjectFieldMapper(String simpleName,
534539
int ignoreAbove,
535540
int depthLimit,
536541
Settings indexSettings) {
537-
super(simpleName, fieldType, defaultFieldType, indexSettings, MultiFields.empty(), CopyTo.empty());
542+
super(simpleName, fieldType, defaultFieldType, indexSettings, CopyTo.empty());
538543
assert fieldType.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS) <= 0;
539544

540545
this.depthLimit = depthLimit;
@@ -564,6 +569,7 @@ public RootFlatObjectFieldType fieldType() {
564569
return (RootFlatObjectFieldType) super.fieldType();
565570
}
566571

572+
@Override
567573
public KeyedFlatObjectFieldType keyedFieldType(String key) {
568574
return new KeyedFlatObjectFieldType(keyedFieldName(), key, fieldType());
569575
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.plugin.mapper;
21+
22+
import org.elasticsearch.index.mapper.FlatObjectFieldMapper;
23+
import org.elasticsearch.index.mapper.Mapper;
24+
import org.elasticsearch.plugins.MapperPlugin;
25+
import org.elasticsearch.plugins.Plugin;
26+
import org.elasticsearch.plugins.SearchPlugin;
27+
28+
import java.util.Map;
29+
30+
import static java.util.Collections.singletonMap;
31+
32+
public class FlattenedMapperPlugin extends Plugin implements MapperPlugin, SearchPlugin {
33+
34+
@Override
35+
public Map<String, Mapper.TypeParser> getMappers() {
36+
return singletonMap(FlatObjectFieldMapper.CONTENT_TYPE, new FlatObjectFieldMapper.TypeParser());
37+
}
38+
39+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.mapper;
21+
22+
import org.elasticsearch.Version;
23+
import org.elasticsearch.common.settings.Settings;
24+
import org.elasticsearch.index.fielddata.AtomicFieldData;
25+
import org.elasticsearch.index.fielddata.IndexFieldData;
26+
import org.elasticsearch.index.fielddata.ScriptDocValues;
27+
import org.elasticsearch.index.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType;
28+
import org.elasticsearch.search.lookup.LeafDocLookup;
29+
import org.elasticsearch.search.lookup.SearchLookup;
30+
import org.elasticsearch.test.ESTestCase;
31+
32+
import java.util.Arrays;
33+
import java.util.HashSet;
34+
import java.util.Set;
35+
import java.util.function.Function;
36+
37+
import static java.util.Collections.emptyList;
38+
import static java.util.Collections.singletonList;
39+
import static org.hamcrest.Matchers.instanceOf;
40+
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
41+
import static org.mockito.Matchers.anyObject;
42+
import static org.mockito.Mockito.doReturn;
43+
import static org.mockito.Mockito.mock;
44+
import static org.mockito.Mockito.when;
45+
46+
public class FlatObjectFieldLookupTests extends ESTestCase {
47+
48+
public void testFieldTypeLookup() {
49+
String fieldName = "object1.object2.field";
50+
FlatObjectFieldMapper mapper = createFlatObjectMapper(fieldName);
51+
52+
FieldTypeLookup lookup = new FieldTypeLookup()
53+
.copyAndAddAll("type", singletonList(mapper), emptyList());
54+
assertEquals(mapper.fieldType(), lookup.get(fieldName));
55+
56+
String objectKey = "key1.key2";
57+
String searchFieldName = fieldName + "." + objectKey;
58+
59+
MappedFieldType searchFieldType = lookup.get(searchFieldName);
60+
assertEquals(mapper.keyedFieldName(), searchFieldType.name());
61+
assertThat(searchFieldType, instanceOf(KeyedFlatObjectFieldType.class));
62+
63+
FlatObjectFieldMapper.KeyedFlatObjectFieldType keyedFieldType = (KeyedFlatObjectFieldType) searchFieldType;
64+
assertEquals(objectKey, keyedFieldType.key());
65+
}
66+
67+
public void testFieldTypeLookupWithAlias() {
68+
String fieldName = "object1.object2.field";
69+
FlatObjectFieldMapper mapper = createFlatObjectMapper(fieldName);
70+
71+
String aliasName = "alias";
72+
FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, fieldName);
73+
74+
FieldTypeLookup lookup = new FieldTypeLookup()
75+
.copyAndAddAll("type", singletonList(mapper), singletonList(alias));
76+
assertEquals(mapper.fieldType(), lookup.get(aliasName));
77+
78+
String objectKey = "key1.key2";
79+
String searchFieldName = aliasName + "." + objectKey;
80+
81+
MappedFieldType searchFieldType = lookup.get(searchFieldName);
82+
assertEquals(mapper.keyedFieldName(), searchFieldType.name());
83+
assertThat(searchFieldType, instanceOf(KeyedFlatObjectFieldType.class));
84+
85+
KeyedFlatObjectFieldType keyedFieldType = (KeyedFlatObjectFieldType) searchFieldType;
86+
assertEquals(objectKey, keyedFieldType.key());
87+
}
88+
89+
public void testFieldTypeLookupWithMultipleFields() {
90+
String field1 = "object1.object2.field";
91+
String field2 = "object1.field";
92+
String field3 = "object2.field";
93+
94+
FlatObjectFieldMapper mapper1 = createFlatObjectMapper(field1);
95+
FlatObjectFieldMapper mapper2 = createFlatObjectMapper(field2);
96+
FlatObjectFieldMapper mapper3 = createFlatObjectMapper(field3);
97+
98+
FieldTypeLookup lookup = new FieldTypeLookup()
99+
.copyAndAddAll("type", Arrays.asList(mapper1, mapper2), emptyList());
100+
assertNotNull(lookup.get(field1 + ".some.key"));
101+
assertNotNull(lookup.get(field2 + ".some.key"));
102+
103+
lookup = lookup.copyAndAddAll("type", singletonList(mapper3), emptyList());
104+
assertNotNull(lookup.get(field1 + ".some.key"));
105+
assertNotNull(lookup.get(field2 + ".some.key"));
106+
assertNotNull(lookup.get(field3 + ".some.key"));
107+
}
108+
109+
public void testMaxDynamicKeyDepth() {
110+
FieldTypeLookup lookup = new FieldTypeLookup();
111+
assertEquals(0, lookup.maxKeyedLookupDepth());
112+
113+
// Add a flattened object field.
114+
String flatObjectName = "object1.object2.field";
115+
FlatObjectFieldMapper flatObjectField = createFlatObjectMapper(flatObjectName);
116+
lookup = lookup.copyAndAddAll("type", singletonList(flatObjectField), emptyList());
117+
assertEquals(3, lookup.maxKeyedLookupDepth());
118+
119+
// Add a short alias to that field.
120+
String aliasName = "alias";
121+
FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, flatObjectName);
122+
lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(alias));
123+
assertEquals(3, lookup.maxKeyedLookupDepth());
124+
125+
// Add a longer alias to that field.
126+
String longAliasName = "object1.object2.object3.alias";
127+
FieldAliasMapper longAlias = new FieldAliasMapper(longAliasName, longAliasName, flatObjectName);
128+
lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(longAlias));
129+
assertEquals(4, lookup.maxKeyedLookupDepth());
130+
131+
// Update the long alias to refer to a non-flattened object field.
132+
String fieldName = "field";
133+
MockFieldMapper field = new MockFieldMapper(fieldName);
134+
longAlias = new FieldAliasMapper(longAliasName, longAliasName, fieldName);
135+
lookup = lookup.copyAndAddAll("type", singletonList(field), singletonList(longAlias));
136+
assertEquals(3, lookup.maxKeyedLookupDepth());
137+
}
138+
139+
public void testFieldLookupIterator() {
140+
MockFieldMapper mapper = new MockFieldMapper("foo");
141+
FlatObjectFieldMapper flatObjectMapper = createFlatObjectMapper("object1.object2.field");
142+
143+
FieldTypeLookup lookup = new FieldTypeLookup()
144+
.copyAndAddAll("type", Arrays.asList(mapper, flatObjectMapper), emptyList());
145+
146+
Set<String> fieldNames = new HashSet<>();
147+
for (MappedFieldType fieldType : lookup) {
148+
fieldNames.add(fieldType.name());
149+
}
150+
151+
assertThat(fieldNames, containsInAnyOrder(
152+
mapper.name(), flatObjectMapper.name(), flatObjectMapper.keyedFieldName()));
153+
}
154+
155+
private FlatObjectFieldMapper createFlatObjectMapper(String fieldName) {
156+
Settings settings = Settings.builder()
157+
.put("index.version.created", Version.CURRENT)
158+
.build();
159+
Mapper.BuilderContext context = new Mapper.BuilderContext(settings, new ContentPath());
160+
return new FlatObjectFieldMapper.Builder(fieldName).build(context);
161+
}
162+
163+
public void testScriptDocValuesLookup() {
164+
MapperService mapperService = mock(MapperService.class);
165+
166+
ScriptDocValues<?> docValues1 = mock(ScriptDocValues.class);
167+
IndexFieldData<?> fieldData1 = createFieldData(docValues1);
168+
169+
ScriptDocValues<?> docValues2 = mock(ScriptDocValues.class);
170+
IndexFieldData<?> fieldData2 = createFieldData(docValues2);
171+
172+
KeyedFlatObjectFieldType fieldType1 = new KeyedFlatObjectFieldType("key1");
173+
when(mapperService.fullName("json.key1")).thenReturn(fieldType1);
174+
175+
KeyedFlatObjectFieldType fieldType2 = new KeyedFlatObjectFieldType( "key2");
176+
when(mapperService.fullName("json.key2")).thenReturn(fieldType2);
177+
178+
Function<MappedFieldType, IndexFieldData<?>> fieldDataSupplier = fieldType -> {
179+
KeyedFlatObjectFieldType keyedFieldType = (KeyedFlatObjectFieldType) fieldType;
180+
return keyedFieldType.key().equals("key1") ? fieldData1 : fieldData2;
181+
};
182+
183+
SearchLookup searchLookup = new SearchLookup(mapperService, fieldDataSupplier);
184+
LeafDocLookup docLookup = searchLookup.doc().getLeafDocLookup(null);
185+
186+
assertEquals(docValues1, docLookup.get("json.key1"));
187+
assertEquals(docValues2, docLookup.get("json.key2"));
188+
}
189+
190+
private IndexFieldData<?> createFieldData(ScriptDocValues<?> scriptDocValues) {
191+
AtomicFieldData atomicFieldData = mock(AtomicFieldData.class);
192+
doReturn(scriptDocValues).when(atomicFieldData).getScriptValues();
193+
194+
IndexFieldData<?> fieldData = mock(IndexFieldData.class);
195+
when(fieldData.getFieldName()).thenReturn("field");
196+
doReturn(atomicFieldData).when(fieldData).load(anyObject());
197+
198+
return fieldData;
199+
}
200+
}

server/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldMapperTests.java renamed to modules/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldMapperTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.index.IndexService;
3232
import org.elasticsearch.index.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType;
3333
import org.elasticsearch.index.mapper.FlatObjectFieldMapper.RootFlatObjectFieldType;
34+
import org.elasticsearch.plugin.mapper.FlattenedMapperPlugin;
3435
import org.elasticsearch.plugins.Plugin;
3536
import org.elasticsearch.test.ESSingleNodeTestCase;
3637
import org.elasticsearch.test.InternalSettingsPlugin;
@@ -55,7 +56,7 @@ public void setup() {
5556

5657
@Override
5758
protected Collection<Class<? extends Plugin>> getPlugins() {
58-
return pluginList(InternalSettingsPlugin.class);
59+
return pluginList(FlattenedMapperPlugin.class, InternalSettingsPlugin.class);
5960
}
6061

6162
public void testDefaults() throws Exception {

0 commit comments

Comments
 (0)