/*
* 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.search.nested;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
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 java.io.IOException;
import java.util.Collection;
import java.util.Set;
/**
* A special query that accepts a top level parent matching query, and returns the nested docs of the matching parent
* doc as well. This is handy when deleting by query, don't use it for other purposes.
*/
public class IncludeNestedDocsQuery extends Query {
private final BitSetProducer parentFilter;
private final Query parentQuery;
// If we are rewritten, this is the original childQuery we
// were passed; we use this for .equals() and
// .hashCode(). This makes rewritten query equal the
// original, so that user does not have to .rewrite() their
// query before searching:
private final Query origParentQuery;
public IncludeNestedDocsQuery(Query parentQuery, BitSetProducer parentFilter) {
this.origParentQuery = parentQuery;
this.parentQuery = parentQuery;
this.parentFilter = parentFilter;
}
// For rewriting
IncludeNestedDocsQuery(Query rewrite, Query originalQuery, IncludeNestedDocsQuery previousInstance) {
this.origParentQuery = originalQuery;
this.parentQuery = rewrite;
this.parentFilter = previousInstance.parentFilter;
setBoost(previousInstance.getBoost());
}
// For cloning
IncludeNestedDocsQuery(Query originalQuery, IncludeNestedDocsQuery previousInstance) {
this.origParentQuery = originalQuery;
this.parentQuery = originalQuery;
this.parentFilter = previousInstance.parentFilter;
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return new IncludeNestedDocsWeight(this, parentQuery, parentQuery.createWeight(searcher, needsScores), parentFilter);
}
static class IncludeNestedDocsWeight extends Weight {
private final Query parentQuery;
private final Weight parentWeight;
private final BitSetProducer parentsFilter;
IncludeNestedDocsWeight(Query query, Query parentQuery, Weight parentWeight, BitSetProducer parentsFilter) {
super(query);
this.parentQuery = parentQuery;
this.parentWeight = parentWeight;
this.parentsFilter = parentsFilter;
}
@Override
public void extractTerms(Set<Term> terms) {
parentWeight.extractTerms(terms);
}
@Override
public void normalize(float norm, float topLevelBoost) {
parentWeight.normalize(norm, topLevelBoost);
}
@Override
public float getValueForNormalization() throws IOException {
return parentWeight.getValueForNormalization(); // this query is never boosted so just delegate...
}
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
final Scorer parentScorer = parentWeight.scorer(context);
// no matches
if (parentScorer == null) {
return null;
}
BitSet parents = parentsFilter.getBitSet(context);
if (parents == null) {
// No matches
return null;
}
int firstParentDoc = parentScorer.iterator().nextDoc();
if (firstParentDoc == DocIdSetIterator.NO_MORE_DOCS) {
// No matches
return null;
}
return new IncludeNestedDocsScorer(this, parentScorer, parents, firstParentDoc);
}
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
return null; //Query is used internally and not by users, so explain can be empty
}
}
static class IncludeNestedDocsScorer extends Scorer {
final Scorer parentScorer;
final BitSet parentBits;
int currentChildPointer = -1;
int currentParentPointer = -1;
int currentDoc = -1;
IncludeNestedDocsScorer(Weight weight, Scorer parentScorer, BitSet parentBits, int currentParentPointer) {
super(weight);
this.parentScorer = parentScorer;
this.parentBits = parentBits;
this.currentParentPointer = currentParentPointer;
if (currentParentPointer == 0) {
currentChildPointer = 0;
} else {
this.currentChildPointer = this.parentBits.prevSetBit(currentParentPointer - 1);
if (currentChildPointer == -1) {
// no previous set parent, we delete from doc 0
currentChildPointer = 0;
} else {
currentChildPointer++; // we only care about children
}
}
currentDoc = currentChildPointer;
}
@Override
public Collection<ChildScorer> getChildren() {
return parentScorer.getChildren();
}
@Override
public DocIdSetIterator iterator() {
final DocIdSetIterator parentIterator = parentScorer.iterator();
return new DocIdSetIterator() {
@Override
public int docID() {
return currentDoc;
}
@Override
public int nextDoc() throws IOException {
if (currentParentPointer == NO_MORE_DOCS) {
return (currentDoc = NO_MORE_DOCS);
}
if (currentChildPointer == currentParentPointer) {
// we need to return the current parent as well, but prepare to return
// the next set of children
currentDoc = currentParentPointer;
currentParentPointer = parentIterator.nextDoc();
if (currentParentPointer != NO_MORE_DOCS) {
currentChildPointer = parentBits.prevSetBit(currentParentPointer - 1);
if (currentChildPointer == -1) {
// no previous set parent, just set the child to the current parent
currentChildPointer = currentParentPointer;
} else {
currentChildPointer++; // we only care about children
}
}
} else {
currentDoc = currentChildPointer++;
}
assert currentDoc != -1;
return currentDoc;
}
@Override
public int advance(int target) throws IOException {
if (target == NO_MORE_DOCS) {
return (currentDoc = NO_MORE_DOCS);
}
if (target == 0) {
return nextDoc();
}
if (target < currentParentPointer) {
currentDoc = currentParentPointer = parentIterator.advance(target);
if (currentParentPointer == NO_MORE_DOCS) {
return (currentDoc = NO_MORE_DOCS);
}
if (currentParentPointer == 0) {
currentChildPointer = 0;
} else {
currentChildPointer = parentBits.prevSetBit(currentParentPointer - 1);
if (currentChildPointer == -1) {
// no previous set parent, just set the child to 0 to delete all up to the parent
currentChildPointer = 0;
} else {
currentChildPointer++; // we only care about children
}
}
} else {
currentDoc = currentChildPointer++;
}
return currentDoc;
}
@Override
public long cost() {
return parentIterator.cost();
}
};
}
@Override
public float score() throws IOException {
return parentScorer.score();
}
@Override
public int freq() throws IOException {
return parentScorer.freq();
}
@Override
public int docID() {
return currentDoc;
}
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
final Query parentRewrite = parentQuery.rewrite(reader);
if (parentRewrite != parentQuery) {
return new IncludeNestedDocsQuery(parentRewrite, parentQuery, this);
} else {
return this;
}
}
@Override
public String toString(String field) {
return "IncludeNestedDocsQuery (" + parentQuery.toString() + ")";
}
@Override
public boolean equals(Object _other) {
if (_other instanceof IncludeNestedDocsQuery) {
final IncludeNestedDocsQuery other = (IncludeNestedDocsQuery) _other;
return origParentQuery.equals(other.origParentQuery) && parentFilter.equals(other.parentFilter);
} else {
return false;
}
}
@Override
public int hashCode() {
final int prime = 31;
int hash = 1;
hash = prime * hash + origParentQuery.hashCode();
hash = prime * hash + parentFilter.hashCode();
return hash;
}
@Override
public Query clone() {
Query clonedQuery = origParentQuery.clone();
return new IncludeNestedDocsQuery(clonedQuery, this);
}
}