/*
* Licensed to Crate under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial
* agreement.
*/
package io.crate.lucene;
import io.crate.analyze.symbol.Function;
import io.crate.data.Input;
import io.crate.operation.collect.collectors.CollectorFieldsVisitor;
import io.crate.operation.projectors.InputCondition;
import io.crate.operation.reference.doc.lucene.CollectorContext;
import io.crate.operation.reference.doc.lucene.LuceneCollectorExpression;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
/**
* Query implementation which filters docIds by evaluating {@code condition} on each docId to verify if it matches.
*
* This query is very slow.
*/
class GenericFunctionQuery extends Query {
private final Function function;
private final LuceneCollectorExpression[] expressions;
private final CollectorContext collectorContext;
private final Input<Boolean> condition;
GenericFunctionQuery(Function function,
Collection<? extends LuceneCollectorExpression<?>> expressions,
CollectorContext collectorContext,
Input<Boolean> condition) {
this.function = function;
// inner loop iterates over expressions - call toArray to avoid iterator allocations
this.expressions = expressions.toArray(new LuceneCollectorExpression[0]);
this.collectorContext = collectorContext;
this.condition = condition;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GenericFunctionQuery that = (GenericFunctionQuery) o;
return function.equals(that.function);
}
@Override
public int hashCode() {
return function.hashCode();
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return new Weight(this) {
@Override
public void extractTerms(Set<Term> terms) {
}
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
final Scorer s = scorer(context);
final boolean match;
final TwoPhaseIterator twoPhase = s.twoPhaseIterator();
if (twoPhase == null) {
match = s.iterator().advance(doc) == doc;
} else {
match = twoPhase.approximation().advance(doc) == doc && twoPhase.matches();
}
if (match) {
assert s.score() == 0f : "score must be 0";
return Explanation.match(0f, "Match on id " + doc);
} else {
return Explanation.match(0f, "No match on id " + doc);
}
}
@Override
public float getValueForNormalization() throws IOException {
return 0;
}
@Override
public void normalize(float norm, float boost) {
}
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
return new ConstantScoreScorer(this, 0f, getTwoPhaseIterator(context));
}
};
}
private FilteredTwoPhaseIterator getTwoPhaseIterator(final LeafReaderContext context) throws IOException {
for (LuceneCollectorExpression expression : expressions) {
expression.setNextReader(context);
}
return new FilteredTwoPhaseIterator(context.reader(), collectorContext.visitor(), condition, expressions);
}
@Override
public String toString(String field) {
return function.toString();
}
private static class FilteredTwoPhaseIterator extends TwoPhaseIterator {
private final LeafReader reader;
private final CollectorFieldsVisitor fieldsVisitor;
private final Input<Boolean> condition;
private final LuceneCollectorExpression[] expressions;
private final boolean fieldsVisitorEnabled;
FilteredTwoPhaseIterator(LeafReader reader,
@Nullable CollectorFieldsVisitor fieldsVisitor,
Input<Boolean> condition,
LuceneCollectorExpression[] expressions) {
super(DocIdSetIterator.all(reader.maxDoc()));
this.reader = reader;
this.fieldsVisitor = fieldsVisitor;
this.fieldsVisitorEnabled = fieldsVisitor != null && fieldsVisitor.required();
this.condition = condition;
this.expressions = expressions;
}
@Override
public boolean matches() throws IOException {
int doc = approximation.docID();
if (fieldsVisitorEnabled) {
fieldsVisitor.reset();
try {
reader.document(doc, fieldsVisitor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (LuceneCollectorExpression expression : expressions) {
expression.setNextDocId(doc);
}
return InputCondition.matches(condition);
}
@Override
public float matchCost() {
// Arbitrary number, we don't have a way to get the cost of the condition
return 10;
}
}
}