org.elasticsearch.search.NestedDocuments Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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;
}
}
}