diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java new file mode 100644 index 0000000000000..cc7b55289bfe0 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java @@ -0,0 +1,146 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.mapper; + +import org.elasticsearch.common.collect.CopyOnWriteHashMap; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +/** + * A container that supports looking up field types for 'dynamic key' fields ({@link DynamicKeyFieldMapper}). + * + * Compared to standard fields, 'dynamic key' fields require special handling. Given a field name of the form + * 'path_to_field.path_to_key', the container will dynamically return a new {@link MappedFieldType} that is + * suitable for performing searches on the sub-key. + * + * Note: we anticipate that 'flattened' fields will be the only implementation {@link DynamicKeyFieldMapper}. + * Flattened object fields live in the 'mapper-flattened' module. + */ +class DynamicKeyFieldTypeLookup { + private final CopyOnWriteHashMap mappers; + private final Map aliasToConcreteName; + + /** + * The maximum field depth of any dynamic key mapper. Allows us to stop searching for + * a dynamic key mapper as soon as we've passed the maximum possible field depth. + */ + private final int maxKeyDepth; + + DynamicKeyFieldTypeLookup() { + this.mappers = new CopyOnWriteHashMap<>(); + this.aliasToConcreteName = Collections.emptyMap(); + this.maxKeyDepth = 0; + } + + private DynamicKeyFieldTypeLookup(CopyOnWriteHashMap mappers, + Map aliasToConcreteName, + int maxKeyDepth) { + this.mappers = mappers; + this.aliasToConcreteName = aliasToConcreteName; + this.maxKeyDepth = maxKeyDepth; + } + + DynamicKeyFieldTypeLookup copyAndAddAll(Map newMappers, + Map aliasToConcreteName) { + CopyOnWriteHashMap combinedMappers = this.mappers.copyAndPutAll(newMappers); + int maxKeyDepth = getMaxKeyDepth(combinedMappers, aliasToConcreteName); + return new DynamicKeyFieldTypeLookup(combinedMappers, aliasToConcreteName, maxKeyDepth); + } + + /** + * Check if the given field corresponds to a dynamic key mapper of the + * form 'path_to_field.path_to_key'. If so, returns a field type that + * can be used to perform searches on this field. Otherwise returns null. + */ + MappedFieldType get(String field) { + if (mappers.isEmpty()) { + return null; + } + + int dotIndex = -1; + int fieldDepth = 0; + + while (true) { + if (++fieldDepth > maxKeyDepth) { + return null; + } + + dotIndex = field.indexOf('.', dotIndex + 1); + if (dotIndex < 0) { + return null; + } + + String parentField = field.substring(0, dotIndex); + String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField); + DynamicKeyFieldMapper mapper = mappers.get(concreteField); + + if (mapper != null) { + String key = field.substring(dotIndex + 1); + return mapper.keyedFieldType(key); + } + } + } + + Iterator fieldTypes() { + return mappers.values().stream() + .map(mapper -> mapper.keyedFieldType("")) + .iterator(); + } + + // Visible for testing. + static int getMaxKeyDepth(Map dynamicKeyMappers, + Map aliasToConcreteName) { + int maxFieldDepth = 0; + for (Map.Entry entry : aliasToConcreteName.entrySet()) { + String aliasName = entry.getKey(); + String path = entry.getValue(); + if (dynamicKeyMappers.containsKey(path)) { + maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName)); + } + } + + for (String fieldName : dynamicKeyMappers.keySet()) { + if (dynamicKeyMappers.containsKey(fieldName)) { + maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName)); + } + } + + return maxFieldDepth; + } + + /** + * Computes the total depth of this field by counting the number of parent fields + * in its path. As an example, the field 'parent1.parent2.field' has depth 3. + */ + private static int fieldDepth(String field) { + int numDots = 0; + int dotIndex = -1; + while (true) { + dotIndex = field.indexOf('.', dotIndex + 1); + if (dotIndex < 0) { + break; + } + numDots++; + } + return numDots + 1; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java index f26e8f3a1ee89..caa698e445abc 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.regex.Regex; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -37,31 +38,21 @@ class FieldTypeLookup implements Iterable { final CopyOnWriteHashMap fullNameToFieldType; private final CopyOnWriteHashMap aliasToConcreteName; + private final DynamicKeyFieldTypeLookup dynamicKeyLookup; - private final CopyOnWriteHashMap dynamicKeyMappers; - - /** - * The maximum field depth of any mapper that implements {@link DynamicKeyFieldMapper}. - * Allows us stop searching for a 'dynamic key' mapper as soon as we've passed the maximum - * possible field depth. - */ - private final int maxDynamicKeyDepth; FieldTypeLookup() { fullNameToFieldType = new CopyOnWriteHashMap<>(); aliasToConcreteName = new CopyOnWriteHashMap<>(); - dynamicKeyMappers = new CopyOnWriteHashMap<>(); - maxDynamicKeyDepth = 0; + dynamicKeyLookup = new DynamicKeyFieldTypeLookup(); } private FieldTypeLookup(CopyOnWriteHashMap fullNameToFieldType, CopyOnWriteHashMap aliasToConcreteName, - CopyOnWriteHashMap dynamicKeyMappers, - int maxDynamicKeyDepth) { + DynamicKeyFieldTypeLookup dynamicKeyLookup) { this.fullNameToFieldType = fullNameToFieldType; this.aliasToConcreteName = aliasToConcreteName; - this.dynamicKeyMappers = dynamicKeyMappers; - this.maxDynamicKeyDepth = maxDynamicKeyDepth; + this.dynamicKeyLookup = dynamicKeyLookup; } /** @@ -80,7 +71,7 @@ public FieldTypeLookup copyAndAddAll(String type, CopyOnWriteHashMap fullName = this.fullNameToFieldType; CopyOnWriteHashMap aliases = this.aliasToConcreteName; - CopyOnWriteHashMap dynamicKeyMappers = this.dynamicKeyMappers; + Map dynamicKeyMappers = new HashMap<>(); for (FieldMapper fieldMapper : fieldMappers) { String fieldName = fieldMapper.name(); @@ -92,8 +83,7 @@ public FieldTypeLookup copyAndAddAll(String type, } if (fieldMapper instanceof DynamicKeyFieldMapper) { - DynamicKeyFieldMapper dynamicKeyMapper = (DynamicKeyFieldMapper) fieldMapper; - dynamicKeyMappers = dynamicKeyMappers.copyAndPut(fieldName, dynamicKeyMapper); + dynamicKeyMappers.put(fieldName, (DynamicKeyFieldMapper) fieldMapper); } } @@ -103,46 +93,8 @@ public FieldTypeLookup copyAndAddAll(String type, aliases = aliases.copyAndPut(aliasName, path); } - int maxDynamicKeyDepth = getMaxDynamicKeyDepth(aliases, dynamicKeyMappers); - - return new FieldTypeLookup(fullName, aliases, dynamicKeyMappers, maxDynamicKeyDepth); - } - - private static int getMaxDynamicKeyDepth(CopyOnWriteHashMap aliases, - CopyOnWriteHashMap dynamicKeyMappers) { - int maxFieldDepth = 0; - for (Map.Entry entry : aliases.entrySet()) { - String aliasName = entry.getKey(); - String path = entry.getValue(); - if (dynamicKeyMappers.containsKey(path)) { - maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName)); - } - } - - for (String fieldName : dynamicKeyMappers.keySet()) { - if (dynamicKeyMappers.containsKey(fieldName)) { - maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName)); - } - } - - return maxFieldDepth; - } - - /** - * Computes the total depth of this field by counting the number of parent fields - * in its path. As an example, the field 'parent1.parent2.field' has depth 3. - */ - private static int fieldDepth(String field) { - int numDots = 0; - int dotIndex = -1; - while (true) { - dotIndex = field.indexOf('.', dotIndex + 1); - if (dotIndex < 0) { - break; - } - numDots++; - } - return numDots + 1; + DynamicKeyFieldTypeLookup newDynamicKeyLookup = this.dynamicKeyLookup.copyAndAddAll(dynamicKeyMappers, aliases); + return new FieldTypeLookup(fullName, aliases, newDynamicKeyLookup); } /** @@ -157,37 +109,7 @@ public MappedFieldType get(String field) { // If the mapping contains fields that support dynamic sub-key lookup, check // if this could correspond to a keyed field of the form 'path_to_field.path_to_key'. - return !dynamicKeyMappers.isEmpty() ? getKeyedFieldType(field) : null; - } - - /** - * Check if the given field corresponds to a dynamic lookup mapper of the - * form 'path_to_field.path_to_key'. If so, returns a field type that - * can be used to perform searches on this field. - */ - private MappedFieldType getKeyedFieldType(String field) { - int dotIndex = -1; - int fieldDepth = 0; - - while (true) { - if (++fieldDepth > maxDynamicKeyDepth) { - return null; - } - - dotIndex = field.indexOf('.', dotIndex + 1); - if (dotIndex < 0) { - return null; - } - - String parentField = field.substring(0, dotIndex); - String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField); - DynamicKeyFieldMapper mapper = dynamicKeyMappers.get(concreteField); - - if (mapper != null) { - String key = field.substring(dotIndex + 1); - return mapper.keyedFieldType(key); - } - } + return dynamicKeyLookup.get(field); } /** @@ -211,19 +133,7 @@ public Set simpleMatchToFullName(String pattern) { @Override public Iterator iterator() { Iterator concreteFieldTypes = fullNameToFieldType.values().iterator(); - - if (dynamicKeyMappers.isEmpty()) { - return concreteFieldTypes; - } else { - Iterator keyedFieldTypes = dynamicKeyMappers.values().stream() - .map(mapper -> mapper.keyedFieldType("")) - .iterator(); - return Iterators.concat(concreteFieldTypes, keyedFieldTypes); - } - } - - // Visible for testing. - int maxKeyedLookupDepth() { - return maxDynamicKeyDepth; + Iterator keyedFieldTypes = dynamicKeyLookup.fieldTypes(); + return Iterators.concat(concreteFieldTypes, keyedFieldTypes); } } diff --git a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java index 8aec1b3044050..6e5076ab2804b 100644 --- a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java +++ b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java @@ -18,7 +18,9 @@ import org.elasticsearch.xpack.flattened.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -95,33 +97,30 @@ public void testFieldTypeLookupWithMultipleFields() { } public void testMaxDynamicKeyDepth() { - FieldTypeLookup lookup = new FieldTypeLookup(); - assertEquals(0, lookup.maxKeyedLookupDepth()); + Map mappers = new HashMap<>(); + Map aliases = new HashMap<>(); + assertEquals(0, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); // Add a flattened object field. String flatObjectName = "object1.object2.field"; FlatObjectFieldMapper flatObjectField = createFlatObjectMapper(flatObjectName); - lookup = lookup.copyAndAddAll("type", singletonList(flatObjectField), emptyList()); - assertEquals(3, lookup.maxKeyedLookupDepth()); + mappers.put(flatObjectName, flatObjectField); + assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); // Add a short alias to that field. String aliasName = "alias"; - FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, flatObjectName); - lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(alias)); - assertEquals(3, lookup.maxKeyedLookupDepth()); + aliases.put(aliasName, flatObjectName); + assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); // Add a longer alias to that field. String longAliasName = "object1.object2.object3.alias"; - FieldAliasMapper longAlias = new FieldAliasMapper(longAliasName, longAliasName, flatObjectName); - lookup = lookup.copyAndAddAll("type", emptyList(), singletonList(longAlias)); - assertEquals(4, lookup.maxKeyedLookupDepth()); + aliases.put(longAliasName, flatObjectName); + assertEquals(4, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); // Update the long alias to refer to a non-flattened object field. String fieldName = "field"; - MockFieldMapper field = new MockFieldMapper(fieldName); - longAlias = new FieldAliasMapper(longAliasName, longAliasName, fieldName); - lookup = lookup.copyAndAddAll("type", singletonList(field), singletonList(longAlias)); - assertEquals(3, lookup.maxKeyedLookupDepth()); + aliases.put(longAliasName, fieldName); + assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); } public void testFieldLookupIterator() {