Skip to content

Commit bcb2305

Browse files
committed
In FieldTypeLookup, factor out flat object field logic. (#52091)
Currently, the logic for looking up `flattened` field types lives in the top-level `FieldTypeLookup`. This PR moves it into a dedicated class `DynamicKeyFieldTypeLookup`.
1 parent 748feaf commit bcb2305

File tree

3 files changed

+171
-116
lines changed

3 files changed

+171
-116
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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.common.collect.CopyOnWriteHashMap;
23+
24+
import java.util.Collections;
25+
import java.util.Iterator;
26+
import java.util.Map;
27+
28+
/**
29+
* A container that supports looking up field types for 'dynamic key' fields ({@link DynamicKeyFieldMapper}).
30+
*
31+
* Compared to standard fields, 'dynamic key' fields require special handling. Given a field name of the form
32+
* 'path_to_field.path_to_key', the container will dynamically return a new {@link MappedFieldType} that is
33+
* suitable for performing searches on the sub-key.
34+
*
35+
* Note: we anticipate that 'flattened' fields will be the only implementation {@link DynamicKeyFieldMapper}.
36+
* Flattened object fields live in the 'mapper-flattened' module.
37+
*/
38+
class DynamicKeyFieldTypeLookup {
39+
private final CopyOnWriteHashMap<String, DynamicKeyFieldMapper> mappers;
40+
private final Map<String, String> aliasToConcreteName;
41+
42+
/**
43+
* The maximum field depth of any dynamic key mapper. Allows us to stop searching for
44+
* a dynamic key mapper as soon as we've passed the maximum possible field depth.
45+
*/
46+
private final int maxKeyDepth;
47+
48+
DynamicKeyFieldTypeLookup() {
49+
this.mappers = new CopyOnWriteHashMap<>();
50+
this.aliasToConcreteName = Collections.emptyMap();
51+
this.maxKeyDepth = 0;
52+
}
53+
54+
private DynamicKeyFieldTypeLookup(CopyOnWriteHashMap<String, DynamicKeyFieldMapper> mappers,
55+
Map<String, String> aliasToConcreteName,
56+
int maxKeyDepth) {
57+
this.mappers = mappers;
58+
this.aliasToConcreteName = aliasToConcreteName;
59+
this.maxKeyDepth = maxKeyDepth;
60+
}
61+
62+
DynamicKeyFieldTypeLookup copyAndAddAll(Map<String, DynamicKeyFieldMapper> newMappers,
63+
Map<String, String> aliasToConcreteName) {
64+
CopyOnWriteHashMap<String, DynamicKeyFieldMapper> combinedMappers = this.mappers.copyAndPutAll(newMappers);
65+
int maxKeyDepth = getMaxKeyDepth(combinedMappers, aliasToConcreteName);
66+
return new DynamicKeyFieldTypeLookup(combinedMappers, aliasToConcreteName, maxKeyDepth);
67+
}
68+
69+
/**
70+
* Check if the given field corresponds to a dynamic key mapper of the
71+
* form 'path_to_field.path_to_key'. If so, returns a field type that
72+
* can be used to perform searches on this field. Otherwise returns null.
73+
*/
74+
MappedFieldType get(String field) {
75+
if (mappers.isEmpty()) {
76+
return null;
77+
}
78+
79+
int dotIndex = -1;
80+
int fieldDepth = 0;
81+
82+
while (true) {
83+
if (++fieldDepth > maxKeyDepth) {
84+
return null;
85+
}
86+
87+
dotIndex = field.indexOf('.', dotIndex + 1);
88+
if (dotIndex < 0) {
89+
return null;
90+
}
91+
92+
String parentField = field.substring(0, dotIndex);
93+
String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField);
94+
DynamicKeyFieldMapper mapper = mappers.get(concreteField);
95+
96+
if (mapper != null) {
97+
String key = field.substring(dotIndex + 1);
98+
return mapper.keyedFieldType(key);
99+
}
100+
}
101+
}
102+
103+
Iterator<MappedFieldType> fieldTypes() {
104+
return mappers.values().stream()
105+
.<MappedFieldType>map(mapper -> mapper.keyedFieldType(""))
106+
.iterator();
107+
}
108+
109+
// Visible for testing.
110+
static int getMaxKeyDepth(Map<String, DynamicKeyFieldMapper> dynamicKeyMappers,
111+
Map<String, String> aliasToConcreteName) {
112+
int maxFieldDepth = 0;
113+
for (Map.Entry<String, String> entry : aliasToConcreteName.entrySet()) {
114+
String aliasName = entry.getKey();
115+
String path = entry.getValue();
116+
if (dynamicKeyMappers.containsKey(path)) {
117+
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName));
118+
}
119+
}
120+
121+
for (String fieldName : dynamicKeyMappers.keySet()) {
122+
if (dynamicKeyMappers.containsKey(fieldName)) {
123+
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName));
124+
}
125+
}
126+
127+
return maxFieldDepth;
128+
}
129+
130+
/**
131+
* Computes the total depth of this field by counting the number of parent fields
132+
* in its path. As an example, the field 'parent1.parent2.field' has depth 3.
133+
*/
134+
private static int fieldDepth(String field) {
135+
int numDots = 0;
136+
int dotIndex = -1;
137+
while (true) {
138+
dotIndex = field.indexOf('.', dotIndex + 1);
139+
if (dotIndex < 0) {
140+
break;
141+
}
142+
numDots++;
143+
}
144+
return numDots + 1;
145+
}
146+
}

server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java

Lines changed: 12 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.regex.Regex;
2525

2626
import java.util.Collection;
27+
import java.util.HashMap;
2728
import java.util.HashSet;
2829
import java.util.Iterator;
2930
import java.util.Map;
@@ -37,31 +38,21 @@ class FieldTypeLookup implements Iterable<MappedFieldType> {
3738

3839
final CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType;
3940
private final CopyOnWriteHashMap<String, String> aliasToConcreteName;
41+
private final DynamicKeyFieldTypeLookup dynamicKeyLookup;
4042

41-
private final CopyOnWriteHashMap<String, DynamicKeyFieldMapper> dynamicKeyMappers;
42-
43-
/**
44-
* The maximum field depth of any mapper that implements {@link DynamicKeyFieldMapper}.
45-
* Allows us stop searching for a 'dynamic key' mapper as soon as we've passed the maximum
46-
* possible field depth.
47-
*/
48-
private final int maxDynamicKeyDepth;
4943

5044
FieldTypeLookup() {
5145
fullNameToFieldType = new CopyOnWriteHashMap<>();
5246
aliasToConcreteName = new CopyOnWriteHashMap<>();
53-
dynamicKeyMappers = new CopyOnWriteHashMap<>();
54-
maxDynamicKeyDepth = 0;
47+
dynamicKeyLookup = new DynamicKeyFieldTypeLookup();
5548
}
5649

5750
private FieldTypeLookup(CopyOnWriteHashMap<String, MappedFieldType> fullNameToFieldType,
5851
CopyOnWriteHashMap<String, String> aliasToConcreteName,
59-
CopyOnWriteHashMap<String, DynamicKeyFieldMapper> dynamicKeyMappers,
60-
int maxDynamicKeyDepth) {
52+
DynamicKeyFieldTypeLookup dynamicKeyLookup) {
6153
this.fullNameToFieldType = fullNameToFieldType;
6254
this.aliasToConcreteName = aliasToConcreteName;
63-
this.dynamicKeyMappers = dynamicKeyMappers;
64-
this.maxDynamicKeyDepth = maxDynamicKeyDepth;
55+
this.dynamicKeyLookup = dynamicKeyLookup;
6556
}
6657

6758
/**
@@ -80,7 +71,7 @@ public FieldTypeLookup copyAndAddAll(String type,
8071

8172
CopyOnWriteHashMap<String, MappedFieldType> fullName = this.fullNameToFieldType;
8273
CopyOnWriteHashMap<String, String> aliases = this.aliasToConcreteName;
83-
CopyOnWriteHashMap<String, DynamicKeyFieldMapper> dynamicKeyMappers = this.dynamicKeyMappers;
74+
Map<String, DynamicKeyFieldMapper> dynamicKeyMappers = new HashMap<>();
8475

8576
for (FieldMapper fieldMapper : fieldMappers) {
8677
String fieldName = fieldMapper.name();
@@ -92,8 +83,7 @@ public FieldTypeLookup copyAndAddAll(String type,
9283
}
9384

9485
if (fieldMapper instanceof DynamicKeyFieldMapper) {
95-
DynamicKeyFieldMapper dynamicKeyMapper = (DynamicKeyFieldMapper) fieldMapper;
96-
dynamicKeyMappers = dynamicKeyMappers.copyAndPut(fieldName, dynamicKeyMapper);
86+
dynamicKeyMappers.put(fieldName, (DynamicKeyFieldMapper) fieldMapper);
9787
}
9888
}
9989

@@ -103,46 +93,8 @@ public FieldTypeLookup copyAndAddAll(String type,
10393
aliases = aliases.copyAndPut(aliasName, path);
10494
}
10595

106-
int maxDynamicKeyDepth = getMaxDynamicKeyDepth(aliases, dynamicKeyMappers);
107-
108-
return new FieldTypeLookup(fullName, aliases, dynamicKeyMappers, maxDynamicKeyDepth);
109-
}
110-
111-
private static int getMaxDynamicKeyDepth(CopyOnWriteHashMap<String, String> aliases,
112-
CopyOnWriteHashMap<String, DynamicKeyFieldMapper> dynamicKeyMappers) {
113-
int maxFieldDepth = 0;
114-
for (Map.Entry<String, String> entry : aliases.entrySet()) {
115-
String aliasName = entry.getKey();
116-
String path = entry.getValue();
117-
if (dynamicKeyMappers.containsKey(path)) {
118-
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName));
119-
}
120-
}
121-
122-
for (String fieldName : dynamicKeyMappers.keySet()) {
123-
if (dynamicKeyMappers.containsKey(fieldName)) {
124-
maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName));
125-
}
126-
}
127-
128-
return maxFieldDepth;
129-
}
130-
131-
/**
132-
* Computes the total depth of this field by counting the number of parent fields
133-
* in its path. As an example, the field 'parent1.parent2.field' has depth 3.
134-
*/
135-
private static int fieldDepth(String field) {
136-
int numDots = 0;
137-
int dotIndex = -1;
138-
while (true) {
139-
dotIndex = field.indexOf('.', dotIndex + 1);
140-
if (dotIndex < 0) {
141-
break;
142-
}
143-
numDots++;
144-
}
145-
return numDots + 1;
96+
DynamicKeyFieldTypeLookup newDynamicKeyLookup = this.dynamicKeyLookup.copyAndAddAll(dynamicKeyMappers, aliases);
97+
return new FieldTypeLookup(fullName, aliases, newDynamicKeyLookup);
14698
}
14799

148100
/**
@@ -157,37 +109,7 @@ public MappedFieldType get(String field) {
157109

158110
// If the mapping contains fields that support dynamic sub-key lookup, check
159111
// if this could correspond to a keyed field of the form 'path_to_field.path_to_key'.
160-
return !dynamicKeyMappers.isEmpty() ? getKeyedFieldType(field) : null;
161-
}
162-
163-
/**
164-
* Check if the given field corresponds to a dynamic lookup mapper of the
165-
* form 'path_to_field.path_to_key'. If so, returns a field type that
166-
* can be used to perform searches on this field.
167-
*/
168-
private MappedFieldType getKeyedFieldType(String field) {
169-
int dotIndex = -1;
170-
int fieldDepth = 0;
171-
172-
while (true) {
173-
if (++fieldDepth > maxDynamicKeyDepth) {
174-
return null;
175-
}
176-
177-
dotIndex = field.indexOf('.', dotIndex + 1);
178-
if (dotIndex < 0) {
179-
return null;
180-
}
181-
182-
String parentField = field.substring(0, dotIndex);
183-
String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField);
184-
DynamicKeyFieldMapper mapper = dynamicKeyMappers.get(concreteField);
185-
186-
if (mapper != null) {
187-
String key = field.substring(dotIndex + 1);
188-
return mapper.keyedFieldType(key);
189-
}
190-
}
112+
return dynamicKeyLookup.get(field);
191113
}
192114

193115
/**
@@ -211,19 +133,7 @@ public Set<String> simpleMatchToFullName(String pattern) {
211133
@Override
212134
public Iterator<MappedFieldType> iterator() {
213135
Iterator<MappedFieldType> concreteFieldTypes = fullNameToFieldType.values().iterator();
214-
215-
if (dynamicKeyMappers.isEmpty()) {
216-
return concreteFieldTypes;
217-
} else {
218-
Iterator<MappedFieldType> keyedFieldTypes = dynamicKeyMappers.values().stream()
219-
.<MappedFieldType>map(mapper -> mapper.keyedFieldType(""))
220-
.iterator();
221-
return Iterators.concat(concreteFieldTypes, keyedFieldTypes);
222-
}
223-
}
224-
225-
// Visible for testing.
226-
int maxKeyedLookupDepth() {
227-
return maxDynamicKeyDepth;
136+
Iterator<MappedFieldType> keyedFieldTypes = dynamicKeyLookup.fieldTypes();
137+
return Iterators.concat(concreteFieldTypes, keyedFieldTypes);
228138
}
229139
}

x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import org.elasticsearch.xpack.flattened.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType;
1919

2020
import java.util.Arrays;
21+
import java.util.HashMap;
2122
import java.util.HashSet;
23+
import java.util.Map;
2224
import java.util.Set;
2325
import java.util.function.Function;
2426

@@ -95,33 +97,30 @@ public void testFieldTypeLookupWithMultipleFields() {
9597
}
9698

9799
public void testMaxDynamicKeyDepth() {
98-
FieldTypeLookup lookup = new FieldTypeLookup();
99-
assertEquals(0, lookup.maxKeyedLookupDepth());
100+
Map<String, DynamicKeyFieldMapper> mappers = new HashMap<>();
101+
Map<String, String> aliases = new HashMap<>();
102+
assertEquals(0, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases));
100103

101104
// Add a flattened object field.
102105
String flatObjectName = "object1.object2.field";
103106
FlatObjectFieldMapper flatObjectField = createFlatObjectMapper(flatObjectName);
104-
lookup = lookup.copyAndAddAll("type", singletonList(flatObjectField), emptyList());
105-
assertEquals(3, lookup.maxKeyedLookupDepth());
107+
mappers.put(flatObjectName, flatObjectField);
108+
assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases));
106109

107110
// Add a short alias to that field.
108111
String aliasName = "alias";
109-
FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, flatObjectName);
110-
lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(alias));
111-
assertEquals(3, lookup.maxKeyedLookupDepth());
112+
aliases.put(aliasName, flatObjectName);
113+
assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases));
112114

113115
// Add a longer alias to that field.
114116
String longAliasName = "object1.object2.object3.alias";
115-
FieldAliasMapper longAlias = new FieldAliasMapper(longAliasName, longAliasName, flatObjectName);
116-
lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(longAlias));
117-
assertEquals(4, lookup.maxKeyedLookupDepth());
117+
aliases.put(longAliasName, flatObjectName);
118+
assertEquals(4, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases));
118119

119120
// Update the long alias to refer to a non-flattened object field.
120121
String fieldName = "field";
121-
MockFieldMapper field = new MockFieldMapper(fieldName);
122-
longAlias = new FieldAliasMapper(longAliasName, longAliasName, fieldName);
123-
lookup = lookup.copyAndAddAll("type", singletonList(field), singletonList(longAlias));
124-
assertEquals(3, lookup.maxKeyedLookupDepth());
122+
aliases.put(longAliasName, fieldName);
123+
assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases));
125124
}
126125

127126
public void testFieldLookupIterator() {

0 commit comments

Comments
 (0)