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

org.elasticsearch.search.NestedDocuments Maven / Gradle / Ivy

/*
 * 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.search;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.join.BitSetProducer;
import org.apache.lucene.util.BitSet;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.index.mapper.MappingLookup;
import org.elasticsearch.index.mapper.NestedObjectMapper;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * Manages loading information about nested documents
 */
public class NestedDocuments {

    private final Map parentObjectFilters = new HashMap<>();
    private final Map childObjectFilters = new HashMap<>();
    private final Map childObjectMappers = new HashMap<>();
    private final BitSetProducer parentDocumentFilter;
    private final MappingLookup mappingLookup;
    private final Version indexVersionCreated;

    /**
     * Create a new NestedDocuments object for an index
     * @param mappingLookup     the index's mapping
     * @param filterProducer    a function to build BitSetProducers from filter queries
     */
    public NestedDocuments(MappingLookup mappingLookup, Version indexVersionCreated, Function filterProducer) {
        this.mappingLookup = mappingLookup;
        this.indexVersionCreated = indexVersionCreated;
        if (mappingLookup.hasNested() == false) {
            this.parentDocumentFilter = null;
        } else {
            this.parentDocumentFilter = filterProducer.apply(Queries.newNonNestedFilter(indexVersionCreated));
            for (NestedObjectMapper mapper : mappingLookup.getNestedParentMappers()) {
                parentObjectFilters.put(mapper.name(), filterProducer.apply(mapper.nestedTypeFilter()));
            }
            for (NestedObjectMapper mapper : mappingLookup.getNestedMappers()) {
                childObjectFilters.put(mapper.name(), null);
                childObjectMappers.put(mapper.name(), mapper);
            }
        }
    }

    /**
     * Returns a LeafNestedDocuments for an index segment
     */
    public LeafNestedDocuments getLeafNestedDocuments(LeafReaderContext ctx) throws IOException {
        if (parentDocumentFilter == null) {
            return LeafNestedDocuments.NO_NESTED_MAPPERS;
        }
        return new HasNestedDocuments(ctx);
    }

    private Weight getNestedChildWeight(LeafReaderContext ctx, String path) throws IOException {
        if (childObjectFilters.containsKey(path) == false || childObjectMappers.containsKey(path) == false) {
            throw new IllegalStateException("Cannot find object mapper for path " + path);
        }
        if (childObjectFilters.get(path) == null) {
            IndexSearcher searcher = new IndexSearcher(ReaderUtil.getTopLevelContext(ctx));
            NestedObjectMapper childMapper = childObjectMappers.get(path);
            childObjectFilters.put(
                path,
                searcher.createWeight(searcher.rewrite(childMapper.nestedTypeFilter()), ScoreMode.COMPLETE_NO_SCORES, 1)
            );
        }
        return childObjectFilters.get(path);
    }

    private class HasNestedDocuments implements LeafNestedDocuments {

        final LeafReaderContext ctx;
        final BitSet parentFilter;
        final Map objectFilters = new HashMap<>();
        final Map childScorers = new HashMap<>();

        int doc = -1;
        int rootDoc = -1;
        SearchHit.NestedIdentity nestedIdentity = null;

        private HasNestedDocuments(LeafReaderContext ctx) throws IOException {
            this.ctx = ctx;
            this.parentFilter = parentDocumentFilter.getBitSet(ctx);
            for (Map.Entry filter : parentObjectFilters.entrySet()) {
                BitSet bits = filter.getValue().getBitSet(ctx);
                if (bits != null) {
                    objectFilters.put(filter.getKey(), bits);
                }
            }
            for (Map.Entry childFilter : childObjectFilters.entrySet()) {
                Scorer scorer = getNestedChildWeight(ctx, childFilter.getKey()).scorer(ctx);
                if (scorer != null) {
                    childScorers.put(childFilter.getKey(), scorer);
                }
            }
        }

        @Override
        public SearchHit.NestedIdentity advance(int doc) throws IOException {
            assert doc >= 0 && doc < ctx.reader().maxDoc();
            if (parentFilter.get(doc)) {
                // parent doc, no nested identity
                this.nestedIdentity = null;
                this.doc = doc;
                this.rootDoc = doc;
                return null;
            } else {
                this.doc = doc;
                this.rootDoc = parentFilter.nextSetBit(doc);
                return this.nestedIdentity = loadNestedIdentity();
            }
        }

        @Override
        public int doc() {
            assert doc != -1 : "Called doc() when unpositioned";
            return doc;
        }

        @Override
        public int rootDoc() {
            assert doc != -1 : "Called rootDoc() when unpositioned";
            return rootDoc;
        }

        @Override
        public SearchHit.NestedIdentity nestedIdentity() {
            assert doc != -1 : "Called nestedIdentity() when unpositioned";
            return nestedIdentity;
        }

        private String findObjectPath(int doc) throws IOException {
            String path = null;
            for (Map.Entry objectFilter : childScorers.entrySet()) {
                DocIdSetIterator it = objectFilter.getValue().iterator();
                if (it.docID() == doc || it.docID() < doc && it.advance(doc) == doc) {
                    if (path == null || path.length() > objectFilter.getKey().length()) {
                        path = objectFilter.getKey();
                    }
                }
            }
            if (path == null) {
                throw new IllegalStateException("Cannot find object path for document " + doc);
            }
            return path;
        }

        private SearchHit.NestedIdentity loadNestedIdentity() throws IOException {
            SearchHit.NestedIdentity ni = null;
            int currentLevelDoc = doc;
            int parentNameLength;
            String path = findObjectPath(doc);
            while (path != null) {
                String parent = mappingLookup.getNestedParent(path);
                // We have to pull a new scorer for each document here, because we advance from
                // the last parent which will be behind the doc
                Scorer childScorer = getNestedChildWeight(ctx, path).scorer(ctx);
                if (childScorer == null) {
                    throw new IllegalStateException("Cannot find object mapper for path " + path + " in doc " + doc);
                }
                BitSet parentBitSet;
                if (parent == null) {
                    parentBitSet = parentFilter;
                    parentNameLength = 0;
                } else {
                    if (objectFilters.containsKey(parent) == false) {
                        throw new IllegalStateException("Cannot find parent mapper for path " + path + " in doc " + doc);
                    }
                    parentBitSet = objectFilters.get(parent);
                    parentNameLength = parent.length() + 1;
                }
                int offset = 0;
                DocIdSetIterator childIt = childScorer.iterator();
                if (indexVersionCreated.onOrAfter(Version.V_6_5_0)) {
                    /*
                     * Starts from the previous parent and finds the offset of the
                     * nestedSubDocID within the nested children. Nested documents
                     * are indexed in the same order than in the source array so the offset
                     * of the nested child is the number of nested document with the same parent
                     * that appear before him.
                     */
                    int lastParent = parentBitSet.prevSetBit(currentLevelDoc);
                    for (int i = childIt.advance(lastParent + 1); i < currentLevelDoc; i = childIt.nextDoc()) {
                        offset++;
                    }
                } else {
                    /*
                     * Nested documents are in reverse order in this version so we start from the current nested document
                     * and find the number of documents with the same parent that appear after it.
                     */
                    int nextParent = parentBitSet.nextSetBit(currentLevelDoc);
                    for (int docId = childIt.advance(currentLevelDoc + 1); docId < nextParent; docId = childIt.nextDoc()) {
                        offset++;
                    }
                }
                ni = new SearchHit.NestedIdentity(path.substring(parentNameLength), offset, ni);
                path = parent;
                currentLevelDoc = parentBitSet.nextSetBit(currentLevelDoc);
            }
            return ni;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy