All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.index.mapper.MappingLookup Maven / Gradle / Ivy

There is a newer version: 9.0.0-beta1
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.index.mapper;

import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.analysis.NamedAnalyzer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * A (mostly) immutable snapshot of the current mapping of an index with
 * access to everything we need for the search phase.
 */
public final class MappingLookup {
    /**
     * Key for the lookup to be used in caches.
     */
    public static class CacheKey {
        private CacheKey() {}
    }

    /**
     * A lookup representing an empty mapping.
     */
    public static final MappingLookup EMPTY = new MappingLookup(
        Mapping.EMPTY,
        org.elasticsearch.common.collect.List.of(),
        org.elasticsearch.common.collect.List.of(),
        org.elasticsearch.common.collect.List.of(),
        null,
        null,
        null
    );

    private final CacheKey cacheKey = new CacheKey();

    /** Full field name to mapper */
    private final Map fieldMappers;
    private final Map objectMappers;
    private final boolean hasNested;
    private final FieldTypeLookup fieldTypeLookup;
    private final FieldTypeLookup indexTimeLookup;  // for index-time scripts, a lookup that does not include runtime fields
    private final Map indexAnalyzersMap = new HashMap<>();
    private final List indexTimeScriptMappers = new ArrayList<>();
    private final DocumentParser documentParser;
    private final Mapping mapping;
    private final IndexSettings indexSettings;
    private final IndexAnalyzers indexAnalyzers;

    public static MappingLookup fromMapping(Mapping mapping,
                                            DocumentParser documentParser,
                                            IndexSettings indexSettings,
                                            IndexAnalyzers indexAnalyzers) {
        List newObjectMappers = new ArrayList<>();
        List newFieldMappers = new ArrayList<>();
        List newFieldAliasMappers = new ArrayList<>();
        for (MetadataFieldMapper metadataMapper : mapping.getSortedMetadataMappers()) {
            if (metadataMapper != null) {
                newFieldMappers.add(metadataMapper);
            }
        }
        for (Mapper child : mapping.getRoot()) {
            collect(child, newObjectMappers, newFieldMappers, newFieldAliasMappers);
        }
        return new MappingLookup(
            mapping,
            newFieldMappers,
            newObjectMappers,
            newFieldAliasMappers,
            documentParser,
            indexSettings,
            indexAnalyzers
        );
    }

    private static void collect(Mapper mapper, Collection objectMappers,
                               Collection fieldMappers,
                               Collection fieldAliasMappers) {
        if (mapper instanceof ObjectMapper) {
            objectMappers.add((ObjectMapper)mapper);
        } else if (mapper instanceof FieldMapper) {
            fieldMappers.add((FieldMapper)mapper);
        } else if (mapper instanceof FieldAliasMapper) {
            fieldAliasMappers.add((FieldAliasMapper) mapper);
        } else {
            throw new IllegalStateException("Unrecognized mapper type [" + mapper.getClass().getSimpleName() + "].");
        }

        for (Mapper child : mapper) {
            collect(child, objectMappers, fieldMappers, fieldAliasMappers);
        }
    }

    public MappingLookup(Mapping mapping,
                         Collection mappers,
                         Collection objectMappers,
                         Collection aliasMappers,
                         DocumentParser documentParser,
                         IndexSettings indexSettings,
                         IndexAnalyzers indexAnalyzers) {
        this.mapping = mapping;
        this.indexSettings = indexSettings;
        this.indexAnalyzers = indexAnalyzers;
        this.documentParser = documentParser;
        Map fieldMappers = new HashMap<>();
        Map objects = new HashMap<>();

        boolean hasNested = false;
        for (ObjectMapper mapper : objectMappers) {
            if (objects.put(mapper.fullPath(), mapper) != null) {
                throw new MapperParsingException("Object mapper [" + mapper.fullPath() + "] is defined more than once");
            }
            if (mapper.nested().isNested()) {
                hasNested = true;
            }
        }
        this.hasNested = hasNested;

        for (FieldMapper mapper : mappers) {
            if (objects.containsKey(mapper.name())) {
                throw new MapperParsingException("Field [" + mapper.name() + "] is defined both as an object and a field");
            }
            if (fieldMappers.put(mapper.name(), mapper) != null) {
                throw new MapperParsingException("Field [" + mapper.name() + "] is defined more than once");
            }
            indexAnalyzersMap.putAll(mapper.indexAnalyzers());
            if (mapper.hasScript()) {
                indexTimeScriptMappers.add(mapper);
            }
        }

        for (FieldAliasMapper aliasMapper : aliasMappers) {
            if (objects.containsKey(aliasMapper.name())) {
                throw new MapperParsingException("Alias [" + aliasMapper.name() + "] is defined both as an object and an alias");
            }
            if (fieldMappers.put(aliasMapper.name(), aliasMapper) != null) {
                throw new MapperParsingException("Alias [" + aliasMapper.name() + "] is defined both as an alias and a concrete field");
            }
        }

        this.fieldTypeLookup = new FieldTypeLookup(mapping.type(), mappers, aliasMappers, mapping.getRoot().runtimeFields());
        this.indexTimeLookup = indexTimeScriptMappers.isEmpty()
            ? null
            : new FieldTypeLookup(mapping.type(), mappers, aliasMappers, Collections.emptyList());
        this.fieldMappers = Collections.unmodifiableMap(fieldMappers);
        this.objectMappers = Collections.unmodifiableMap(objects);
    }

    /**
     * Returns the leaf mapper associated with this field name. Note that the returned mapper
     * could be either a concrete {@link FieldMapper}, or a {@link FieldAliasMapper}.
     *
     * To access a field's type information, {@link MapperService#fieldType} should be used instead.
     */
    public Mapper getMapper(String field) {
        return fieldMappers.get(field);
    }

    FieldTypeLookup fieldTypesLookup() {
        return fieldTypeLookup;
    }

    FieldTypeLookup indexTimeLookup() {
        return indexTimeLookup;
    }

    List indexTimeScriptMappers() {
        return indexTimeScriptMappers;
    }

    public NamedAnalyzer indexAnalyzer(String field, Function unmappedFieldAnalyzer) {
        if (this.indexAnalyzersMap.containsKey(field)) {
            return this.indexAnalyzersMap.get(field);
        }
        return unmappedFieldAnalyzer.apply(field);
    }

    /**
     * Returns an iterable over all the registered field mappers (including alias mappers)
     */
    public Iterable fieldMappers() {
        return fieldMappers.values();
    }

    /**
     * Returns the registered mapped field types.
     */
    public Collection fieldTypes() {
        return fieldTypeLookup.get();
    }

    void checkLimits(IndexSettings settings) {
        checkFieldLimit(settings.getMappingTotalFieldsLimit());
        checkObjectDepthLimit(settings.getMappingDepthLimit());
        checkFieldNameLengthLimit(settings.getMappingFieldNameLengthLimit());
        checkNestedLimit(settings.getMappingNestedFieldsLimit());
    }

    private void checkFieldLimit(long limit) {
        if (fieldMappers.size() + objectMappers.size() - mapping.getSortedMetadataMappers().length > limit) {
            throw new IllegalArgumentException("Limit of total fields [" + limit + "] has been exceeded");
        }
    }

    private void checkObjectDepthLimit(long limit) {
        for (String objectPath : objectMappers.keySet()) {
            int numDots = 0;
            for (int i = 0; i < objectPath.length(); ++i) {
                if (objectPath.charAt(i) == '.') {
                    numDots += 1;
                }
            }
            final int depth = numDots + 2;
            if (depth > limit) {
                throw new IllegalArgumentException("Limit of mapping depth [" + limit +
                    "] has been exceeded due to object field [" + objectPath + "]");
            }
        }
    }

    private void checkFieldNameLengthLimit(long limit) {
        Stream.of(objectMappers.values().stream(), fieldMappers.values().stream())
            .reduce(Stream::concat)
            .orElseGet(Stream::empty)
            .forEach(mapper -> {
                String name = mapper.simpleName();
                if (name.length() > limit) {
                    throw new IllegalArgumentException("Field name [" + name + "] is longer than the limit of [" + limit + "] characters");
                }
            });
    }

    private void checkNestedLimit(long limit) {
        long actualNestedFields = 0;
        for (ObjectMapper objectMapper : objectMappers.values()) {
            if (objectMapper.nested().isNested()) {
                actualNestedFields++;
            }
        }
        if (actualNestedFields > limit) {
            throw new IllegalArgumentException("Limit of nested fields [" + limit + "] has been exceeded");
        }
    }

    public boolean hasNested() {
        return hasNested;
    }

    public Map objectMappers() {
        return objectMappers;
    }

    public boolean isMultiField(String field) {
        String sourceParent = parentObject(field);
        return sourceParent != null && fieldMappers.containsKey(sourceParent);
    }

    public boolean isObjectField(String field) {
        return objectMappers.containsKey(field);
    }

    public String getNestedScope(String path) {
        for (String parentPath = parentObject(path); parentPath != null; parentPath = parentObject(parentPath)) {
            ObjectMapper objectMapper = objectMappers.get(parentPath);
            if (objectMapper != null && objectMapper.nested().isNested()) {
                return parentPath;
            }
        }
        return null;
    }

    private static String parentObject(String field) {
        int lastDot = field.lastIndexOf('.');
        if (lastDot == -1) {
            return null;
        }
        return field.substring(0, lastDot);
    }

    public Set simpleMatchToFullName(String pattern) {
        return fieldTypesLookup().simpleMatchToFullName(pattern);
    }

    /**
     * Returns the mapped field type for the given field name.
     */
    public MappedFieldType getFieldType(String field) {
        return fieldTypesLookup().get(field);
    }

    /**
     * Given a concrete field name, return its paths in the _source.
     *
     * For most fields, the source path is the same as the field itself. However
     * there are cases where a field's values are found elsewhere in the _source:
     *   - For a multi-field, the source path is the parent field.
     *   - One field's content could have been copied to another through copy_to.
     *
     * @param field The field for which to look up the _source path. Note that the field
     *              should be a concrete field and *not* an alias.
     * @return A set of paths in the _source that contain the field's values.
     */
    public Set sourcePaths(String field) {
        return fieldTypesLookup().sourcePaths(field);
    }

    public ParsedDocument parseDocument(SourceToParse source) {
        return documentParser.parseDocument(source, this);
    }

    public boolean hasMappings() {
        return this != EMPTY;
    }

    public boolean isSourceEnabled() {
        SourceFieldMapper sfm = mapping.getMetadataMapperByClass(SourceFieldMapper.class);
        return sfm != null && sfm.enabled();
    }

    /**
     * Key for the lookup to be used in caches.
     */
    public CacheKey cacheKey() {
        return cacheKey;
    }

    public String getType() {
        return mapping.type();
    }

    Mapping getMapping() {
        return mapping;
    }

    IndexSettings getIndexSettings() {
        return indexSettings;
    }

    IndexAnalyzers getIndexAnalyzers() {
        return indexAnalyzers;
    }

    /**
     * Given an object path, checks to see if any of its parents are non-nested objects
     */
    public boolean hasNonNestedParent(String path) {
        ObjectMapper mapper = objectMappers().get(path);
        if (mapper == null) {
            return false;
        }
        while (mapper != null) {
            if (mapper.nested().isNested() == false) {
                return true;
            }
            if (path.contains(".") == false) {
                return false;
            }
            path = path.substring(0, path.lastIndexOf("."));
            mapper = objectMappers().get(path);
        }
        return false;
    }

    /**
     * Returns all nested object mappers
     */
    public List getNestedMappers() {
        List childMappers = new ArrayList<>();
        for (ObjectMapper mapper : objectMappers().values()) {
            if (mapper.nested().isNested() == false) {
                continue;
            }
            childMappers.add(mapper);
        }
        return childMappers;
    }

    /**
     * Returns all nested object mappers which contain further nested object mappers
     *
     * Used by BitSetProducerWarmer
     */
    public List getNestedParentMappers() {
        List parents = new ArrayList<>();
        for (ObjectMapper mapper : objectMappers().values()) {
            String nestedParentPath = getNestedParent(mapper.fullPath());
            if (nestedParentPath == null) {
                continue;
            }
            ObjectMapper parent = objectMappers().get(nestedParentPath);
            if (parent.nested().isNested()) {
                parents.add(parent);
            }
        }
        return parents;
    }

    /**
     * Given a nested object path, returns the path to its nested parent
     *
     * In particular, if a nested field `foo` contains an object field
     * `bar.baz`, then calling this method with `foo.bar.baz` will return
     * the path `foo`, skipping over the object-but-not-nested `foo.bar`
     */
    public String getNestedParent(String path) {
        ObjectMapper mapper = objectMappers().get(path);
        if (mapper == null) {
            return null;
        }
        if (path.contains(".") == false) {
            return null;
        }
        do {
            path = path.substring(0, path.lastIndexOf("."));
            mapper = objectMappers().get(path);
            if (mapper == null) {
                return null;
            }
            if (mapper.nested().isNested()) {
                return path;
            }
            if (path.contains(".") == false) {
                return null;
            }
        } while(true);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy