Ver Fonte

Merge FunctionScoreQuery and FiltersFunctionScoreQuery (#25889)

This change merges the functionality of the FiltersFunctionScoreQuery in the FunctionScoreQuery.
It also ensures that an exception is thrown when the computed score is equals to Float.NaN or Float.NEGATIVE_INFINITY.
These scores are invalid for TopDocsCollectors that relies on score comparison.

Fixes #15709
Fixes #23628
Jim Ferenczi há 8 anos atrás
pai
commit
562c3744ca
20 ficheiros alterados com 535 adições e 695 exclusões
  1. 0 5
      core/src/main/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighter.java
  2. 0 3
      core/src/main/java/org/apache/lucene/search/vectorhighlight/CustomFieldQuery.java
  3. 0 4
      core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java
  4. 0 366
      core/src/main/java/org/elasticsearch/common/lucene/search/function/FiltersFunctionScoreQuery.java
  5. 310 58
      core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java
  6. 10 1
      core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java
  7. 2 6
      core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java
  8. 1 0
      core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java
  9. 23 25
      core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java
  10. 0 3
      core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/CustomQueryScorer.java
  11. 0 57
      core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java
  12. 25 25
      core/src/test/java/org/elasticsearch/index/query/ScoreModeTests.java
  13. 7 20
      core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreEquivalenceTests.java
  14. 11 11
      core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java
  15. 128 97
      core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreTests.java
  16. 6 7
      core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java
  17. 4 1
      core/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreFieldValueIT.java
  18. 3 3
      core/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreIT.java
  19. 2 2
      modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java
  20. 3 1
      modules/percolator/src/test/java/org/elasticsearch/percolator/QueryAnalyzerTests.java

+ 0 - 5
core/src/main/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighter.java

@@ -20,8 +20,6 @@
 package org.apache.lucene.search.uhighlight;
 
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.IndexOptions;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.CommonTermsQuery;
 import org.apache.lucene.search.DocIdSetIterator;
@@ -39,7 +37,6 @@ import org.apache.lucene.util.automaton.CharacterRunAutomaton;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.lucene.all.AllTermQuery;
 import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 
 import java.io.IOException;
@@ -213,8 +210,6 @@ public class CustomUnifiedHighlighter extends UnifiedHighlighter {
             return Collections.singletonList(new TermQuery(atq.getTerm()));
         } else if (query instanceof FunctionScoreQuery) {
             return Collections.singletonList(((FunctionScoreQuery) query).getSubQuery());
-        } else if (query instanceof FiltersFunctionScoreQuery) {
-            return Collections.singletonList(((FiltersFunctionScoreQuery) query).getSubQuery());
         } else {
             return null;
         }

+ 0 - 3
core/src/main/java/org/apache/lucene/search/vectorhighlight/CustomFieldQuery.java

@@ -32,7 +32,6 @@ import org.apache.lucene.search.SynonymQuery;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.index.search.ESToParentBlockJoinQuery;
 
@@ -69,8 +68,6 @@ public class CustomFieldQuery extends FieldQuery {
             flatten(((FunctionScoreQuery) sourceQuery).getSubQuery(), reader, flatQueries, boost);
         } else if (sourceQuery instanceof MultiPhrasePrefixQuery) {
             flatten(sourceQuery.rewrite(reader), reader, flatQueries, boost);
-        } else if (sourceQuery instanceof FiltersFunctionScoreQuery) {
-            flatten(((FiltersFunctionScoreQuery) sourceQuery).getSubQuery(), reader, flatQueries, boost);
         } else if (sourceQuery instanceof MultiPhraseQuery) {
             MultiPhraseQuery q = ((MultiPhraseQuery) sourceQuery);
             convertMultiPhraseQuery(0, new int[q.getTermArrays().length], q, q.getTermArrays(), q.getPositions(), reader, flatQueries);

+ 0 - 4
core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java

@@ -83,10 +83,6 @@ public class FieldValueFactorFunction extends ScoreFunction {
                 }
                 double val = value * boostFactor;
                 double result = modifier.apply(val);
-                if (Double.isNaN(result) || Double.isInfinite(result)) {
-                    throw new ElasticsearchException("Result of field modification [" + modifier.toString() + "(" + val
-                            + ")] must be a number");
-                }
                 return result;
             }
 

+ 0 - 366
core/src/main/java/org/elasticsearch/common/lucene/search/function/FiltersFunctionScoreQuery.java

@@ -1,366 +0,0 @@
-/*
- * 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.common.lucene.search.function;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.Explanation;
-import org.apache.lucene.search.FilterScorer;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.ScorerSupplier;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.Bits;
-import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.io.stream.Writeable;
-import org.elasticsearch.common.lucene.Lucene;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * A query that allows for a pluggable boost function / filter. If it matches
- * the filter, it will be boosted by the formula.
- */
-public class FiltersFunctionScoreQuery extends Query {
-
-    public static class FilterFunction {
-        public final Query filter;
-        public final ScoreFunction function;
-
-        public FilterFunction(Query filter, ScoreFunction function) {
-            this.filter = filter;
-            this.function = function;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            FilterFunction that = (FilterFunction) o;
-            return Objects.equals(this.filter, that.filter) && Objects.equals(this.function, that.function);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(super.hashCode(), filter, function);
-        }
-    }
-
-    public enum ScoreMode implements Writeable {
-        FIRST, AVG, MAX, SUM, MIN, MULTIPLY;
-
-        @Override
-        public void writeTo(StreamOutput out) throws IOException {
-            out.writeEnum(this);
-        }
-
-        public static ScoreMode readFromStream(StreamInput in) throws IOException {
-            return in.readEnum(ScoreMode.class);
-        }
-
-        public static ScoreMode fromString(String scoreMode) {
-            return valueOf(scoreMode.toUpperCase(Locale.ROOT));
-        }
-    }
-
-    final Query subQuery;
-    final FilterFunction[] filterFunctions;
-    final ScoreMode scoreMode;
-    final float maxBoost;
-    private final Float minScore;
-
-    protected final CombineFunction combineFunction;
-
-    public FiltersFunctionScoreQuery(Query subQuery, ScoreMode scoreMode, FilterFunction[] filterFunctions, float maxBoost, Float minScore, CombineFunction combineFunction) {
-        this.subQuery = subQuery;
-        this.scoreMode = scoreMode;
-        this.filterFunctions = filterFunctions;
-        this.maxBoost = maxBoost;
-        this.combineFunction = combineFunction;
-        this.minScore = minScore;
-    }
-
-    public Query getSubQuery() {
-        return subQuery;
-    }
-
-    public FilterFunction[] getFilterFunctions() {
-        return filterFunctions;
-    }
-
-    @Override
-    public Query rewrite(IndexReader reader) throws IOException {
-        Query rewritten = super.rewrite(reader);
-        if (rewritten != this) {
-            return rewritten;
-        }
-        Query newQ = subQuery.rewrite(reader);
-        if (newQ == subQuery)
-            return this;
-        return new FiltersFunctionScoreQuery(newQ, scoreMode, filterFunctions, maxBoost, minScore, combineFunction);
-    }
-
-    @Override
-    public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-        if (needsScores == false && minScore == null) {
-            return subQuery.createWeight(searcher, needsScores, boost);
-        }
-
-        boolean subQueryNeedsScores = combineFunction != CombineFunction.REPLACE;
-        Weight[] filterWeights = new Weight[filterFunctions.length];
-        for (int i = 0; i < filterFunctions.length; ++i) {
-            subQueryNeedsScores |= filterFunctions[i].function.needsScores();
-            filterWeights[i] = searcher.createNormalizedWeight(filterFunctions[i].filter, false);
-        }
-        Weight subQueryWeight = subQuery.createWeight(searcher, subQueryNeedsScores, boost);
-        return new CustomBoostFactorWeight(this, subQueryWeight, filterWeights, subQueryNeedsScores);
-    }
-
-    class CustomBoostFactorWeight extends Weight {
-
-        final Weight subQueryWeight;
-        final Weight[] filterWeights;
-        final boolean needsScores;
-
-        CustomBoostFactorWeight(Query parent, Weight subQueryWeight, Weight[] filterWeights, boolean needsScores) throws IOException {
-            super(parent);
-            this.subQueryWeight = subQueryWeight;
-            this.filterWeights = filterWeights;
-            this.needsScores = needsScores;
-        }
-
-        @Override
-        public void extractTerms(Set<Term> terms) {
-            subQueryWeight.extractTerms(terms);
-        }
-
-        private FiltersFunctionFactorScorer functionScorer(LeafReaderContext context) throws IOException {
-            Scorer subQueryScorer = subQueryWeight.scorer(context);
-            if (subQueryScorer == null) {
-                return null;
-            }
-            final LeafScoreFunction[] functions = new LeafScoreFunction[filterFunctions.length];
-            final Bits[] docSets = new Bits[filterFunctions.length];
-            for (int i = 0; i < filterFunctions.length; i++) {
-                FilterFunction filterFunction = filterFunctions[i];
-                functions[i] = filterFunction.function.getLeafScoreFunction(context);
-                ScorerSupplier filterScorerSupplier = filterWeights[i].scorerSupplier(context);
-                docSets[i] = Lucene.asSequentialAccessBits(context.reader().maxDoc(), filterScorerSupplier);
-            }
-            return new FiltersFunctionFactorScorer(this, subQueryScorer, scoreMode, filterFunctions, maxBoost, functions, docSets, combineFunction, needsScores);
-        }
-
-        @Override
-        public Scorer scorer(LeafReaderContext context) throws IOException {
-            Scorer scorer = functionScorer(context);
-            if (scorer != null && minScore != null) {
-                scorer = new MinScoreScorer(this, scorer, minScore);
-            }
-            return scorer;
-        }
-
-        @Override
-        public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-
-            Explanation expl = subQueryWeight.explain(context, doc);
-            if (!expl.isMatch()) {
-                return expl;
-            }
-            // First: Gather explanations for all filters
-            List<Explanation> filterExplanations = new ArrayList<>();
-            for (int i = 0; i < filterFunctions.length; ++i) {
-                Bits docSet = Lucene.asSequentialAccessBits(context.reader().maxDoc(),
-                        filterWeights[i].scorerSupplier(context));
-                if (docSet.get(doc)) {
-                    FilterFunction filterFunction = filterFunctions[i];
-                    Explanation functionExplanation = filterFunction.function.getLeafScoreFunction(context).explainScore(doc, expl);
-                    double factor = functionExplanation.getValue();
-                    float sc = (float) factor;
-                    Explanation filterExplanation = Explanation.match(sc, "function score, product of:",
-                            Explanation.match(1.0f, "match filter: " + filterFunction.filter.toString()), functionExplanation);
-                    filterExplanations.add(filterExplanation);
-                }
-            }
-            FiltersFunctionFactorScorer scorer = functionScorer(context);
-            int actualDoc = scorer.iterator().advance(doc);
-            assert (actualDoc == doc);
-            double score = scorer.computeScore(doc, expl.getValue());
-            Explanation factorExplanation;
-            if (filterExplanations.size() > 0) {
-                factorExplanation = Explanation.match(
-                        (float) score,
-                        "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]",
-                        filterExplanations);
-
-            } else {
-                // it is a little weird to add a match although no function matches but that is the way function_score behaves right now
-                factorExplanation = Explanation.match(1.0f,
-                    "No function matched", Collections.emptyList());
-            }
-            expl = combineFunction.explain(expl, factorExplanation, maxBoost);
-            if (minScore != null && minScore > expl.getValue()) {
-                expl = Explanation.noMatch("Score value is too low, expected at least " + minScore + " but got " + expl.getValue(), expl);
-            }
-            return expl;
-        }
-    }
-
-    static class FiltersFunctionFactorScorer extends FilterScorer {
-        private final FilterFunction[] filterFunctions;
-        private final ScoreMode scoreMode;
-        private final LeafScoreFunction[] functions;
-        private final Bits[] docSets;
-        private final CombineFunction scoreCombiner;
-        private final float maxBoost;
-        private final boolean needsScores;
-
-        private FiltersFunctionFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreMode scoreMode, FilterFunction[] filterFunctions,
-                                            float maxBoost, LeafScoreFunction[] functions, Bits[] docSets, CombineFunction scoreCombiner, boolean needsScores) throws IOException {
-            super(scorer, w);
-            this.scoreMode = scoreMode;
-            this.filterFunctions = filterFunctions;
-            this.functions = functions;
-            this.docSets = docSets;
-            this.scoreCombiner = scoreCombiner;
-            this.maxBoost = maxBoost;
-            this.needsScores = needsScores;
-        }
-
-        @Override
-        public float score() throws IOException {
-            int docId = docID();
-            // Even if the weight is created with needsScores=false, it might
-            // be costly to call score(), so we explicitly check if scores
-            // are needed
-            float subQueryScore = needsScores ? super.score() : 0f;
-            double factor = computeScore(docId, subQueryScore);
-            return scoreCombiner.combine(subQueryScore, factor, maxBoost);
-        }
-
-        protected double computeScore(int docId, float subQueryScore) throws IOException {
-            double factor = 1d;
-            switch(scoreMode) {
-                case FIRST:
-                    for (int i = 0; i < filterFunctions.length; i++) {
-                        if (docSets[i].get(docId)) {
-                            factor = functions[i].score(docId, subQueryScore);
-                            break;
-                        }
-                    }
-                    break;
-                case MAX:
-                    double maxFactor = Double.NEGATIVE_INFINITY;
-                    for (int i = 0; i < filterFunctions.length; i++) {
-                        if (docSets[i].get(docId)) {
-                            maxFactor = Math.max(functions[i].score(docId, subQueryScore), maxFactor);
-                        }
-                    }
-                    if (maxFactor != Float.NEGATIVE_INFINITY) {
-                        factor = maxFactor;
-                    }
-                    break;
-                case MIN:
-                    double minFactor = Double.POSITIVE_INFINITY;
-                    for (int i = 0; i < filterFunctions.length; i++) {
-                        if (docSets[i].get(docId)) {
-                            minFactor = Math.min(functions[i].score(docId, subQueryScore), minFactor);
-                        }
-                    }
-                    if (minFactor != Float.POSITIVE_INFINITY) {
-                        factor = minFactor;
-                    }
-                    break;
-                case MULTIPLY:
-                    for (int i = 0; i < filterFunctions.length; i++) {
-                        if (docSets[i].get(docId)) {
-                            factor *= functions[i].score(docId, subQueryScore);
-                        }
-                    }
-                    break;
-                default: // Avg / Total
-                    double totalFactor = 0.0f;
-                    double weightSum = 0;
-                    for (int i = 0; i < filterFunctions.length; i++) {
-                        if (docSets[i].get(docId)) {
-                            totalFactor += functions[i].score(docId, subQueryScore);
-                            if (filterFunctions[i].function instanceof WeightFactorFunction) {
-                                weightSum += ((WeightFactorFunction) filterFunctions[i].function).getWeight();
-                            } else {
-                                weightSum += 1.0;
-                            }
-                        }
-                    }
-                    if (weightSum != 0) {
-                        factor = totalFactor;
-                        if (scoreMode == ScoreMode.AVG) {
-                            factor /= weightSum;
-                        }
-                    }
-                    break;
-            }
-            return factor;
-        }
-    }
-
-    @Override
-    public String toString(String field) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("function score (").append(subQuery.toString(field)).append(", functions: [");
-        for (FilterFunction filterFunction : filterFunctions) {
-            sb.append("{filter(").append(filterFunction.filter).append("), function [").append(filterFunction.function).append("]}");
-        }
-        sb.append("])");
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (sameClassAs(o) == false) {
-            return false;
-        }
-        FiltersFunctionScoreQuery other = (FiltersFunctionScoreQuery) o;
-        return Objects.equals(this.subQuery, other.subQuery) && this.maxBoost == other.maxBoost &&
-            Objects.equals(this.combineFunction, other.combineFunction) && Objects.equals(this.minScore, other.minScore) &&
-            Objects.equals(this.scoreMode, other.scoreMode) &&
-            Arrays.equals(this.filterFunctions, other.filterFunctions);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(classHash(), subQuery, maxBoost, combineFunction, minScore, scoreMode, Arrays.hashCode(filterFunctions));
-    }
-}

+ 310 - 58
core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java

@@ -27,50 +27,167 @@ import org.apache.lucene.search.FilterScorer;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
 import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.search.TopDocsCollector;
+import org.apache.lucene.search.TopScoreDocCollector;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.lucene.Lucene;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 
 /**
- * A query that allows for a pluggable boost function to be applied to it.
+ * A query that allows for a pluggable boost function / filter. If it matches
+ * the filter, it will be boosted by the formula.
  */
 public class FunctionScoreQuery extends Query {
-
     public static final float DEFAULT_MAX_BOOST = Float.MAX_VALUE;
 
+    public static class FilterScoreFunction extends ScoreFunction {
+        public final Query filter;
+        public final ScoreFunction function;
+
+        public FilterScoreFunction(Query filter, ScoreFunction function) {
+            super(function.getDefaultScoreCombiner());
+            this.filter = filter;
+            this.function = function;
+        }
+
+        @Override
+        public LeafScoreFunction getLeafScoreFunction(LeafReaderContext ctx) throws IOException {
+            return function.getLeafScoreFunction(ctx);
+        }
+
+        @Override
+        public boolean needsScores() {
+            return function.needsScores();
+        }
+
+        @Override
+        protected boolean doEquals(ScoreFunction other) {
+            if (getClass() != other.getClass()) {
+                return false;
+            }
+            FilterScoreFunction that = (FilterScoreFunction) other;
+            return Objects.equals(this.filter, that.filter) && Objects.equals(this.function, that.function);
+        }
+
+        @Override
+        protected int doHashCode() {
+            return Objects.hash(filter, function);
+        }
+
+        @Override
+        protected ScoreFunction rewrite(IndexReader reader) throws IOException {
+            Query newFilter = filter.rewrite(reader);
+            if (newFilter == filter) {
+                return this;
+            }
+            return new FilterScoreFunction(newFilter, function);
+        }
+
+        @Override
+        public float getWeight() {
+            return function.getWeight();
+        }
+    }
+
+    public enum ScoreMode implements Writeable {
+        FIRST, AVG, MAX, SUM, MIN, MULTIPLY;
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            out.writeEnum(this);
+        }
+
+        public static ScoreMode readFromStream(StreamInput in) throws IOException {
+            return in.readEnum(ScoreMode.class);
+        }
+
+        public static ScoreMode fromString(String scoreMode) {
+            return valueOf(scoreMode.toUpperCase(Locale.ROOT));
+        }
+    }
+
     final Query subQuery;
-    final ScoreFunction function;
+    final ScoreFunction[] functions;
+    final ScoreMode scoreMode;
     final float maxBoost;
-    final CombineFunction combineFunction;
-    private Float minScore;
+    private final Float minScore;
 
-    public FunctionScoreQuery(Query subQuery, ScoreFunction function, Float minScore, CombineFunction combineFunction, float maxBoost) {
-        this.subQuery = subQuery;
-        this.function = function;
-        this.combineFunction = combineFunction;
-        this.minScore = minScore;
-        this.maxBoost = maxBoost;
+    protected final CombineFunction combineFunction;
+
+    /**
+     * Creates a FunctionScoreQuery without function.
+     * @param subQuery The query to match.
+     * @param minScore The minimum score to consider a document.
+     * @param maxBoost The maximum applicable boost.
+     */
+    public FunctionScoreQuery(Query subQuery, Float minScore, float maxBoost) {
+        this(subQuery, ScoreMode.FIRST, new ScoreFunction[0], CombineFunction.MULTIPLY, minScore, maxBoost);
     }
 
+    /**
+     * Creates a FunctionScoreQuery with a single {@link ScoreFunction}
+     * @param subQuery The query to match.
+     * @param function The {@link ScoreFunction} to apply.
+     */
     public FunctionScoreQuery(Query subQuery, ScoreFunction function) {
-        this.subQuery = subQuery;
-        this.function = function;
-        this.combineFunction = function.getDefaultScoreCombiner();
-        this.maxBoost = DEFAULT_MAX_BOOST;
+        this(subQuery, function, CombineFunction.MULTIPLY, null, DEFAULT_MAX_BOOST);
     }
 
-    public float getMaxBoost() {
-        return this.maxBoost;
+
+    /**
+     * Creates a FunctionScoreQuery with a single function
+     * @param subQuery The query to match.
+     * @param function The {@link ScoreFunction} to apply.
+     * @param combineFunction Defines how the query and function score should be applied.
+     * @param minScore The minimum score to consider a document.
+     * @param maxBoost The maximum applicable boost.
+     */
+    public FunctionScoreQuery(Query subQuery, ScoreFunction function, CombineFunction combineFunction, Float minScore, float maxBoost) {
+        this(subQuery, ScoreMode.FIRST, new ScoreFunction[] { function }, combineFunction, minScore, maxBoost);
+    }
+
+    /**
+     * Creates a FunctionScoreQuery with multiple score functions
+     * @param subQuery The query to match.
+     * @param scoreMode Defines how the different score functions should be combined.
+     * @param functions The {@link ScoreFunction}s to apply.
+     * @param combineFunction Defines how the query and function score should be applied.
+     * @param minScore The minimum score to consider a document.
+     * @param maxBoost The maximum applicable boost.
+     */
+    public FunctionScoreQuery(Query subQuery, ScoreMode scoreMode, ScoreFunction[] functions,
+                              CombineFunction combineFunction, Float minScore, float maxBoost) {
+        if (Arrays.stream(functions).anyMatch(func -> func == null)) {
+            throw new IllegalArgumentException("Score function should not be null");
+        }
+        this.subQuery = subQuery;
+        this.scoreMode = scoreMode;
+        this.functions = functions;
+        this.maxBoost = maxBoost;
+        this.combineFunction = combineFunction;
+        this.minScore = minScore;
     }
 
     public Query getSubQuery() {
         return subQuery;
     }
 
-    public ScoreFunction getFunction() {
-        return function;
+    public ScoreFunction[] getFunctions() {
+        return functions;
     }
 
     public Float getMinScore() {
@@ -84,10 +201,16 @@ public class FunctionScoreQuery extends Query {
             return rewritten;
         }
         Query newQ = subQuery.rewrite(reader);
-        if (newQ == subQuery) {
-            return this;
+        ScoreFunction[] newFunctions = new ScoreFunction[functions.length];
+        boolean needsRewrite = (newQ != subQuery);
+        for (int i = 0; i < functions.length; i++) {
+            newFunctions[i] = functions[i].rewrite(reader);
+            needsRewrite |= (newFunctions[i] != functions[i]);
         }
-        return new FunctionScoreQuery(newQ, function, minScore, combineFunction, maxBoost);
+        if (needsRewrite) {
+            return new FunctionScoreQuery(newQ, scoreMode, newFunctions, combineFunction, minScore, maxBoost);
+        }
+        return this;
     }
 
     @Override
@@ -96,22 +219,29 @@ public class FunctionScoreQuery extends Query {
             return subQuery.createWeight(searcher, needsScores, boost);
         }
 
-        boolean subQueryNeedsScores =
-                combineFunction != CombineFunction.REPLACE // if we don't replace we need the original score
-                || function == null // when the function is null, we just multiply the score, so we need it
-                || function.needsScores(); // some scripts can replace with a script that returns eg. 1/_score
+        boolean subQueryNeedsScores = combineFunction != CombineFunction.REPLACE;
+        Weight[] filterWeights = new Weight[functions.length];
+        for (int i = 0; i < functions.length; ++i) {
+            subQueryNeedsScores |= functions[i].needsScores();
+            if (functions[i] instanceof FilterScoreFunction) {
+                Query filter = ((FilterScoreFunction) functions[i]).filter;
+                filterWeights[i] = searcher.createNormalizedWeight(filter, false);
+            }
+        }
         Weight subQueryWeight = subQuery.createWeight(searcher, subQueryNeedsScores, boost);
-        return new CustomBoostFactorWeight(this, subQueryWeight, subQueryNeedsScores);
+        return new CustomBoostFactorWeight(this, subQueryWeight, filterWeights, subQueryNeedsScores);
     }
 
     class CustomBoostFactorWeight extends Weight {
 
         final Weight subQueryWeight;
+        final Weight[] filterWeights;
         final boolean needsScores;
 
-        CustomBoostFactorWeight(Query parent, Weight subQueryWeight, boolean needsScores) throws IOException {
+        CustomBoostFactorWeight(Query parent, Weight subQueryWeight, Weight[] filterWeights, boolean needsScores) throws IOException {
             super(parent);
             this.subQueryWeight = subQueryWeight;
+            this.filterWeights = filterWeights;
             this.needsScores = needsScores;
         }
 
@@ -125,11 +255,20 @@ public class FunctionScoreQuery extends Query {
             if (subQueryScorer == null) {
                 return null;
             }
-            LeafScoreFunction leafFunction = null;
-            if (function != null) {
-                leafFunction = function.getLeafScoreFunction(context);
+            final LeafScoreFunction[] leafFunctions = new LeafScoreFunction[functions.length];
+            final Bits[] docSets = new Bits[functions.length];
+            for (int i = 0; i < functions.length; i++) {
+                ScoreFunction function = functions[i];
+                leafFunctions[i] = function.getLeafScoreFunction(context);
+                if (filterWeights[i] != null) {
+                    ScorerSupplier filterScorerSupplier = filterWeights[i].scorerSupplier(context);
+                    docSets[i] = Lucene.asSequentialAccessBits(context.reader().maxDoc(), filterScorerSupplier);
+                } else {
+                    docSets[i] = new Bits.MatchAllBits(context.reader().maxDoc());
+                }
             }
-            return new FunctionFactorScorer(this, subQueryScorer, leafFunction, maxBoost, combineFunction, needsScores);
+            return new FunctionFactorScorer(this, subQueryScorer, scoreMode, functions, maxBoost, leafFunctions,
+                docSets, combineFunction, needsScores);
         }
 
         @Override
@@ -143,16 +282,51 @@ public class FunctionScoreQuery extends Query {
 
         @Override
         public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-            Explanation subQueryExpl = subQueryWeight.explain(context, doc);
-            if (!subQueryExpl.isMatch()) {
-                return subQueryExpl;
+
+            Explanation expl = subQueryWeight.explain(context, doc);
+            if (!expl.isMatch()) {
+                return expl;
             }
-            Explanation expl;
-            if (function != null) {
-                Explanation functionExplanation = function.getLeafScoreFunction(context).explainScore(doc, subQueryExpl);
-                expl = combineFunction.explain(subQueryExpl, functionExplanation, maxBoost);
-            } else {
-                expl = subQueryExpl;
+            boolean singleFunction = functions.length == 1 && functions[0] instanceof FilterScoreFunction == false;
+            if (functions.length > 0) {
+                // First: Gather explanations for all functions/filters
+                List<Explanation> functionsExplanations = new ArrayList<>();
+                for (int i = 0; i < functions.length; ++i) {
+                    if (filterWeights[i] != null) {
+                        final Bits docSet = Lucene.asSequentialAccessBits(context.reader().maxDoc(), filterWeights[i].scorerSupplier(context));
+                        if (docSet.get(doc) == false) {
+                            continue;
+                        }
+                    }
+                    ScoreFunction function = functions[i];
+                    Explanation functionExplanation = function.getLeafScoreFunction(context).explainScore(doc, expl);
+                    if (function instanceof FilterScoreFunction) {
+                        double factor = functionExplanation.getValue();
+                        float sc = (float) factor;
+                        Query filterQuery = ((FilterScoreFunction) function).filter;
+                        Explanation filterExplanation = Explanation.match(sc, "function score, product of:",
+                            Explanation.match(1.0f, "match filter: " + filterQuery.toString()), functionExplanation);
+                        functionsExplanations.add(filterExplanation);
+                    } else {
+                        functionsExplanations.add(functionExplanation);
+                    }
+                }
+                final Explanation factorExplanation;
+                if (functionsExplanations.size() == 0) {
+                    // it is a little weird to add a match although no function matches but that is the way function_score behaves right now
+                    factorExplanation = Explanation.match(1.0f, "No function matched", Collections.emptyList());
+                } else if (singleFunction && functionsExplanations.size() == 1) {
+                    factorExplanation = functionsExplanations.get(0);
+                } else {
+                    FunctionFactorScorer scorer = functionScorer(context);
+                    int actualDoc = scorer.iterator().advance(doc);
+                    assert (actualDoc == doc);
+                    double score = scorer.computeScore(doc, expl.getValue());
+                    factorExplanation = Explanation.match(
+                        (float) score,
+                        "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]", functionsExplanations);
+                }
+                expl = combineFunction.explain(expl, factorExplanation, maxBoost);
             }
             if (minScore != null && minScore > expl.getValue()) {
                 expl = Explanation.noMatch("Score value is too low, expected at least " + minScore + " but got " + expl.getValue(), expl);
@@ -162,16 +336,21 @@ public class FunctionScoreQuery extends Query {
     }
 
     static class FunctionFactorScorer extends FilterScorer {
-
-        private final LeafScoreFunction function;
-        private final boolean needsScores;
+        private final ScoreFunction[] functions;
+        private final ScoreMode scoreMode;
+        private final LeafScoreFunction[] leafFunctions;
+        private final Bits[] docSets;
         private final CombineFunction scoreCombiner;
         private final float maxBoost;
+        private final boolean needsScores;
 
-        private FunctionFactorScorer(CustomBoostFactorWeight w, Scorer scorer, LeafScoreFunction function, float maxBoost, CombineFunction scoreCombiner, boolean needsScores)
-                throws IOException {
+        private FunctionFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreMode scoreMode, ScoreFunction[] functions,
+                                     float maxBoost, LeafScoreFunction[] leafFunctions, Bits[] docSets, CombineFunction scoreCombiner, boolean needsScores) throws IOException {
             super(scorer, w);
-            this.function = function;
+            this.scoreMode = scoreMode;
+            this.functions = functions;
+            this.leafFunctions = leafFunctions;
+            this.docSets = docSets;
             this.scoreCombiner = scoreCombiner;
             this.maxBoost = maxBoost;
             this.needsScores = needsScores;
@@ -179,23 +358,95 @@ public class FunctionScoreQuery extends Query {
 
         @Override
         public float score() throws IOException {
+            int docId = docID();
             // Even if the weight is created with needsScores=false, it might
             // be costly to call score(), so we explicitly check if scores
             // are needed
-            float score = needsScores ? super.score() : 0f;
-            if (function == null) {
-                return score;
-            } else {
-                return scoreCombiner.combine(score,
-                        function.score(docID(), score), maxBoost);
+            float subQueryScore = needsScores ? super.score() : 0f;
+            if (leafFunctions.length == 0) {
+                return subQueryScore;
             }
+            double factor = computeScore(docId, subQueryScore);
+            float finalScore = scoreCombiner.combine(subQueryScore, factor, maxBoost);
+            if (finalScore == Float.NEGATIVE_INFINITY || Float.isNaN(finalScore)) {
+                /**
+                 * These scores are invalid for score based {@link TopDocsCollector}s.
+                 * See {@link TopScoreDocCollector} for details.
+                 */
+                throw new ElasticsearchException("function score query returned an invalid score: " + finalScore + " for doc: " + docId);
+            }
+            return finalScore;
+        }
+
+        protected double computeScore(int docId, float subQueryScore) throws IOException {
+            double factor = 1d;
+            switch(scoreMode) {
+                case FIRST:
+                    for (int i = 0; i < leafFunctions.length; i++) {
+                        if (docSets[i].get(docId)) {
+                            factor = leafFunctions[i].score(docId, subQueryScore);
+                            break;
+                        }
+                    }
+                    break;
+                case MAX:
+                    double maxFactor = Double.NEGATIVE_INFINITY;
+                    for (int i = 0; i < leafFunctions.length; i++) {
+                        if (docSets[i].get(docId)) {
+                            maxFactor = Math.max(leafFunctions[i].score(docId, subQueryScore), maxFactor);
+                        }
+                    }
+                    if (maxFactor != Float.NEGATIVE_INFINITY) {
+                        factor = maxFactor;
+                    }
+                    break;
+                case MIN:
+                    double minFactor = Double.POSITIVE_INFINITY;
+                    for (int i = 0; i < leafFunctions.length; i++) {
+                        if (docSets[i].get(docId)) {
+                            minFactor = Math.min(leafFunctions[i].score(docId, subQueryScore), minFactor);
+                        }
+                    }
+                    if (minFactor != Float.POSITIVE_INFINITY) {
+                        factor = minFactor;
+                    }
+                    break;
+                case MULTIPLY:
+                    for (int i = 0; i < leafFunctions.length; i++) {
+                        if (docSets[i].get(docId)) {
+                            factor *= leafFunctions[i].score(docId, subQueryScore);
+                        }
+                    }
+                    break;
+                default: // Avg / Total
+                    double totalFactor = 0.0f;
+                    double weightSum = 0;
+                    for (int i = 0; i < leafFunctions.length; i++) {
+                        if (docSets[i].get(docId)) {
+                            totalFactor += leafFunctions[i].score(docId, subQueryScore);
+                            weightSum += functions[i].getWeight();
+                        }
+                    }
+                    if (weightSum != 0) {
+                        factor = totalFactor;
+                        if (scoreMode == ScoreMode.AVG) {
+                            factor /= weightSum;
+                        }
+                    }
+                    break;
+            }
+            return factor;
         }
     }
 
     @Override
     public String toString(String field) {
         StringBuilder sb = new StringBuilder();
-        sb.append("function score (").append(subQuery.toString(field)).append(",function=").append(function).append(')');
+        sb.append("function score (").append(subQuery.toString(field)).append(", functions: [");
+        for (ScoreFunction function : functions) {
+            sb.append("{" + (function == null ? "" : function.toString()) + "}");
+        }
+        sb.append("])");
         return sb.toString();
     }
 
@@ -208,13 +459,14 @@ public class FunctionScoreQuery extends Query {
             return false;
         }
         FunctionScoreQuery other = (FunctionScoreQuery) o;
-        return Objects.equals(this.subQuery, other.subQuery) && Objects.equals(this.function, other.function)
-                && Objects.equals(this.combineFunction, other.combineFunction)
-                && Objects.equals(this.minScore, other.minScore) && this.maxBoost == other.maxBoost;
+        return Objects.equals(this.subQuery, other.subQuery) && this.maxBoost == other.maxBoost &&
+            Objects.equals(this.combineFunction, other.combineFunction) && Objects.equals(this.minScore, other.minScore) &&
+            Objects.equals(this.scoreMode, other.scoreMode) &&
+            Arrays.equals(this.functions, other.functions);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(classHash(), subQuery.hashCode(), function, combineFunction, minScore, maxBoost);
+        return Objects.hash(classHash(), subQuery, maxBoost, combineFunction, minScore, scoreMode, Arrays.hashCode(functions));
     }
 }

+ 10 - 1
core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.common.lucene.search.function;
 
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReaderContext;
 
 import java.io.IOException;
@@ -40,7 +41,7 @@ public abstract class ScoreFunction {
 
     /**
      * Indicates if document scores are needed by this function.
-     * 
+     *
      * @return {@code true} if scores are needed.
      */
     public abstract boolean needsScores();
@@ -59,6 +60,10 @@ public abstract class ScoreFunction {
                 doEquals(other);
     }
 
+    public float getWeight() {
+        return 1.0f;
+    }
+
     /**
      * Indicates whether some other {@link ScoreFunction} object of the same type is "equal to" this one.
      */
@@ -74,4 +79,8 @@ public abstract class ScoreFunction {
     }
 
     protected abstract int doHashCode();
+
+    protected ScoreFunction rewrite(IndexReader reader) throws IOException {
+        return this;
+    }
 }

+ 2 - 6
core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java

@@ -25,7 +25,6 @@ import org.apache.lucene.search.Explanation;
 import org.apache.lucene.search.Scorer;
 import org.elasticsearch.script.ExplainableSearchScript;
 import org.elasticsearch.script.Script;
-import org.elasticsearch.script.GeneralScriptException;
 import org.elasticsearch.script.SearchScript;
 
 import java.io.IOException;
@@ -80,14 +79,11 @@ public class ScriptScoreFunction extends ScoreFunction {
         leafScript.setScorer(scorer);
         return new LeafScoreFunction() {
             @Override
-            public double score(int docId, float subQueryScore) {
+            public double score(int docId, float subQueryScore) throws IOException {
                 leafScript.setDocument(docId);
                 scorer.docid = docId;
                 scorer.score = subQueryScore;
                 double result = leafScript.runAsDouble();
-                if (Double.isNaN(result)) {
-                    throw new GeneralScriptException("script_score returned NaN");
-                }
                 return result;
             }
 
@@ -137,4 +133,4 @@ public class ScriptScoreFunction extends ScoreFunction {
     protected int doHashCode() {
         return Objects.hash(sScript);
     }
-}
+}

+ 1 - 0
core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java

@@ -75,6 +75,7 @@ public class WeightFactorFunction extends ScoreFunction {
         return Explanation.match(getWeight(), "weight");
     }
 
+    @Override
     public float getWeight() {
         return weight;
     }

+ 23 - 25
core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java

@@ -27,8 +27,6 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.FilterFunction;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.ScoreFunction;
 import org.elasticsearch.common.xcontent.ToXContent;
@@ -70,13 +68,13 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
     public static final ParseField MIN_SCORE_FIELD = new ParseField("min_score");
 
     public static final CombineFunction DEFAULT_BOOST_MODE = CombineFunction.MULTIPLY;
-    public static final FiltersFunctionScoreQuery.ScoreMode DEFAULT_SCORE_MODE = FiltersFunctionScoreQuery.ScoreMode.MULTIPLY;
+    public static final FunctionScoreQuery.ScoreMode DEFAULT_SCORE_MODE = FunctionScoreQuery.ScoreMode.MULTIPLY;
 
     private final QueryBuilder query;
 
     private float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST;
 
-    private FiltersFunctionScoreQuery.ScoreMode scoreMode = DEFAULT_SCORE_MODE;
+    private FunctionScoreQuery.ScoreMode scoreMode = DEFAULT_SCORE_MODE;
 
     private CombineFunction boostMode;
 
@@ -153,7 +151,7 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         maxBoost = in.readFloat();
         minScore = in.readOptionalFloat();
         boostMode = in.readOptionalWriteable(CombineFunction::readFromStream);
-        scoreMode = FiltersFunctionScoreQuery.ScoreMode.readFromStream(in);
+        scoreMode = FunctionScoreQuery.ScoreMode.readFromStream(in);
     }
 
     @Override
@@ -182,9 +180,9 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
 
     /**
      * Score mode defines how results of individual score functions will be aggregated.
-     * @see org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode
+     * @see FunctionScoreQuery.ScoreMode
      */
-    public FunctionScoreQueryBuilder scoreMode(FiltersFunctionScoreQuery.ScoreMode scoreMode) {
+    public FunctionScoreQueryBuilder scoreMode(FunctionScoreQuery.ScoreMode scoreMode) {
         if (scoreMode == null) {
             throw new IllegalArgumentException("[" + NAME + "]  requires 'score_mode' field");
         }
@@ -194,9 +192,9 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
 
     /**
      * Returns the score mode, meaning how results of individual score functions will be aggregated.
-     * @see org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode
+     * @see FunctionScoreQuery.ScoreMode
      */
-    public FiltersFunctionScoreQuery.ScoreMode scoreMode() {
+    public FunctionScoreQuery.ScoreMode scoreMode() {
         return this.scoreMode;
     }
 
@@ -294,12 +292,16 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        FilterFunction[] filterFunctions = new FilterFunction[filterFunctionBuilders.length];
+        ScoreFunction[] filterFunctions = new ScoreFunction[filterFunctionBuilders.length];
         int i = 0;
         for (FilterFunctionBuilder filterFunctionBuilder : filterFunctionBuilders) {
-            Query filter = filterFunctionBuilder.getFilter().toQuery(context);
             ScoreFunction scoreFunction = filterFunctionBuilder.getScoreFunction().toFunction(context);
-            filterFunctions[i++] = new FilterFunction(filter, scoreFunction);
+            if (filterFunctionBuilder.getFilter().getName().equals(MatchAllQueryBuilder.NAME)) {
+                filterFunctions[i++] = scoreFunction;
+            } else {
+                Query filter = filterFunctionBuilder.getFilter().toQuery(context);
+                filterFunctions[i++] = new FunctionScoreQuery.FilterScoreFunction(filter, scoreFunction);
+            }
         }
 
         Query query = this.query.toQuery(context);
@@ -308,22 +310,18 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         }
 
         // handle cases where only one score function and no filter was provided. In this case we create a FunctionScoreQuery.
-        if (filterFunctions.length == 0 || filterFunctions.length == 1
-                && (this.filterFunctionBuilders[0].getFilter().getName().equals(MatchAllQueryBuilder.NAME))) {
-            ScoreFunction function = filterFunctions.length == 0 ? null : filterFunctions[0].function;
+        if (filterFunctions.length == 0) {
+            return new FunctionScoreQuery(query, minScore, maxBoost);
+        } else if (filterFunctions.length == 1 && filterFunctions[0] instanceof FunctionScoreQuery.FilterScoreFunction == false) {
             CombineFunction combineFunction = this.boostMode;
             if (combineFunction == null) {
-                if (function != null) {
-                    combineFunction = function.getDefaultScoreCombiner();
-                } else {
-                    combineFunction = DEFAULT_BOOST_MODE;
-                }
+                combineFunction = filterFunctions[0].getDefaultScoreCombiner();
             }
-            return new FunctionScoreQuery(query, function, minScore, combineFunction, maxBoost);
+            return new FunctionScoreQuery(query, filterFunctions[0], combineFunction, minScore, maxBoost);
         }
-        // in all other cases we create a FiltersFunctionScoreQuery
+        // in all other cases we create a FunctionScoreQuery with filters
         CombineFunction boostMode = this.boostMode == null ? DEFAULT_BOOST_MODE : this.boostMode;
-        return new FiltersFunctionScoreQuery(query, scoreMode, filterFunctions, maxBoost, minScore, boostMode);
+        return new FunctionScoreQuery(query, scoreMode, filterFunctions, boostMode, minScore, maxBoost);
     }
 
     /**
@@ -439,7 +437,7 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         float boost = AbstractQueryBuilder.DEFAULT_BOOST;
         String queryName = null;
 
-        FiltersFunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE;
+        FunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE;
         float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST;
         Float minScore = null;
 
@@ -495,7 +493,7 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
 
             } else if (token.isValue()) {
                 if (SCORE_MODE_FIELD.match(currentFieldName)) {
-                    scoreMode = FiltersFunctionScoreQuery.ScoreMode.fromString(parser.text());
+                    scoreMode = FunctionScoreQuery.ScoreMode.fromString(parser.text());
                 } else if (BOOST_MODE_FIELD.match(currentFieldName)) {
                     combineFunction = CombineFunction.fromString(parser.text());
                 } else if (MAX_BOOST_FIELD.match(currentFieldName)) {

+ 0 - 3
core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/CustomQueryScorer.java

@@ -24,7 +24,6 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.highlight.QueryScorer;
 import org.apache.lucene.search.highlight.WeightedSpanTerm;
 import org.apache.lucene.search.highlight.WeightedSpanTermExtractor;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 
 import java.io.IOException;
@@ -87,8 +86,6 @@ public final class CustomQueryScorer extends QueryScorer {
                 return;
             } else if (query instanceof FunctionScoreQuery) {
                 super.extract(((FunctionScoreQuery) query).getSubQuery(), boost, terms);
-            } else if (query instanceof FiltersFunctionScoreQuery) {
-                super.extract(((FiltersFunctionScoreQuery) query).getSubQuery(), boost, terms);
             } else {
                 super.extract(query, boost, terms);
             }

+ 0 - 57
core/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java

@@ -1,57 +0,0 @@
-/*
- * 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.common.lucene.search.function;
-
-import org.apache.lucene.index.LeafReaderContext;
-import org.elasticsearch.script.GeneralScriptException;
-import org.elasticsearch.script.SearchScript;
-import org.elasticsearch.test.ESTestCase;
-
-import java.io.IOException;
-
-public class ScriptScoreFunctionTests extends ESTestCase {
-    /**
-     * Tests https://github.com/elastic/elasticsearch/issues/2426
-     */
-    public void testScriptScoresReturnsNaN() throws IOException {
-        // script that always returns NaN
-        ScoreFunction scoreFunction = new ScriptScoreFunction(mockScript("Double.NaN"), new SearchScript.LeafFactory() {
-            @Override
-            public SearchScript newInstance(LeafReaderContext context) throws IOException {
-                return new SearchScript(null, null, null) {
-                    @Override
-                    public double runAsDouble() {
-                        return Double.NaN;
-                    }
-                };
-            }
-            
-            @Override
-            public boolean needs_score() {
-                return false;
-            }
-        });
-        LeafScoreFunction leafScoreFunction = scoreFunction.getLeafScoreFunction(null);
-        GeneralScriptException expected = expectThrows(GeneralScriptException.class, () -> {
-            leafScoreFunction.score(randomInt(), randomFloat());
-        });
-        assertTrue(expected.getMessage().contains("returned NaN"));
-    }
-}

+ 25 - 25
core/src/test/java/org/elasticsearch/index/query/ScoreModeTests.java

@@ -21,7 +21,7 @@ package org.elasticsearch.index.query;
 
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.test.ESTestCase;
 
 import static org.hamcrest.Matchers.equalTo;
@@ -29,51 +29,51 @@ import static org.hamcrest.Matchers.equalTo;
 public class ScoreModeTests extends ESTestCase {
 
     public void testValidOrdinals() {
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.FIRST.ordinal(), equalTo(0));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.AVG.ordinal(), equalTo(1));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.MAX.ordinal(), equalTo(2));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.SUM.ordinal(), equalTo(3));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.MIN.ordinal(), equalTo(4));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY.ordinal(), equalTo(5));
+        assertThat(FunctionScoreQuery.ScoreMode.FIRST.ordinal(), equalTo(0));
+        assertThat(FunctionScoreQuery.ScoreMode.AVG.ordinal(), equalTo(1));
+        assertThat(FunctionScoreQuery.ScoreMode.MAX.ordinal(), equalTo(2));
+        assertThat(FunctionScoreQuery.ScoreMode.SUM.ordinal(), equalTo(3));
+        assertThat(FunctionScoreQuery.ScoreMode.MIN.ordinal(), equalTo(4));
+        assertThat(FunctionScoreQuery.ScoreMode.MULTIPLY.ordinal(), equalTo(5));
     }
 
     public void testWriteTo() throws Exception {
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.FIRST.writeTo(out);
+            FunctionScoreQuery.ScoreMode.FIRST.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(0));
             }
         }
 
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.AVG.writeTo(out);
+            FunctionScoreQuery.ScoreMode.AVG.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(1));
             }
         }
 
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.MAX.writeTo(out);
+            FunctionScoreQuery.ScoreMode.MAX.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(2));
             }
         }
 
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.SUM.writeTo(out);
+            FunctionScoreQuery.ScoreMode.SUM.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(3));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.MIN.writeTo(out);
+            FunctionScoreQuery.ScoreMode.MIN.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(4));
             }
         }
 
         try (BytesStreamOutput out = new BytesStreamOutput()) {
-            FiltersFunctionScoreQuery.ScoreMode.MULTIPLY.writeTo(out);
+            FunctionScoreQuery.ScoreMode.MULTIPLY.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 assertThat(in.readVInt(), equalTo(5));
             }
@@ -84,47 +84,47 @@ public class ScoreModeTests extends ESTestCase {
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(0);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.FIRST));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.FIRST));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(1);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.AVG));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.AVG));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(2);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.MAX));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.MAX));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(3);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.SUM));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.SUM));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(4);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.MIN));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.MIN));
             }
         }
         try (BytesStreamOutput out = new BytesStreamOutput()) {
             out.writeVInt(5);
             try (StreamInput in = out.bytes().streamInput()) {
-                assertThat(FiltersFunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY));
+                assertThat(FunctionScoreQuery.ScoreMode.readFromStream(in), equalTo(FunctionScoreQuery.ScoreMode.MULTIPLY));
             }
         }
     }
 
     public void testFromString() {
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("first"), equalTo(FiltersFunctionScoreQuery.ScoreMode.FIRST));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("avg"), equalTo(FiltersFunctionScoreQuery.ScoreMode.AVG));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("max"), equalTo(FiltersFunctionScoreQuery.ScoreMode.MAX));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("sum"), equalTo(FiltersFunctionScoreQuery.ScoreMode.SUM));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("min"), equalTo(FiltersFunctionScoreQuery.ScoreMode.MIN));
-        assertThat(FiltersFunctionScoreQuery.ScoreMode.fromString("multiply"), equalTo(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("first"), equalTo(FunctionScoreQuery.ScoreMode.FIRST));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("avg"), equalTo(FunctionScoreQuery.ScoreMode.AVG));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("max"), equalTo(FunctionScoreQuery.ScoreMode.MAX));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("sum"), equalTo(FunctionScoreQuery.ScoreMode.SUM));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("min"), equalTo(FunctionScoreQuery.ScoreMode.MIN));
+        assertThat(FunctionScoreQuery.ScoreMode.fromString("multiply"), equalTo(FunctionScoreQuery.ScoreMode.MULTIPLY));
     }
 }

+ 7 - 20
core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreEquivalenceTests.java

@@ -25,10 +25,6 @@ import org.apache.lucene.search.RandomApproximationQuery;
 import org.apache.lucene.search.SearchEquivalenceTestBase;
 import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.bootstrap.BootstrapForTesting;
-import org.elasticsearch.common.lucene.search.function.CombineFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.FilterFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 
 public class FunctionScoreEquivalenceTests extends SearchEquivalenceTestBase {
@@ -45,11 +41,10 @@ public class FunctionScoreEquivalenceTests extends SearchEquivalenceTestBase {
         Term term = randomTerm();
         Query query = new TermQuery(term);
 
-        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, 0f, null, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, Float.POSITIVE_INFINITY);
         assertSameScores(query, fsq);
 
-        FiltersFunctionScoreQuery ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY,
-                0f, CombineFunction.MULTIPLY);
+        FunctionScoreQuery ffsq = new FunctionScoreQuery(query, 0f, Float.POSITIVE_INFINITY);
         assertSameScores(query, ffsq);
     }
 
@@ -57,12 +52,8 @@ public class FunctionScoreEquivalenceTests extends SearchEquivalenceTestBase {
         Term term = randomTerm();
         Query query = new TermQuery(term);
 
-        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, Float.POSITIVE_INFINITY, null, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq = new FunctionScoreQuery(query, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
         assertSameScores(new MatchNoDocsQuery(), fsq);
-
-        FiltersFunctionScoreQuery ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY,
-                Float.POSITIVE_INFINITY, CombineFunction.MULTIPLY);
-        assertSameScores(new MatchNoDocsQuery(), ffsq);
     }
 
     public void testTwoPhaseMinScore() throws Exception {
@@ -70,16 +61,12 @@ public class FunctionScoreEquivalenceTests extends SearchEquivalenceTestBase {
         Query query = new TermQuery(term);
         Float minScore = random().nextFloat();
 
-        FunctionScoreQuery fsq1 = new FunctionScoreQuery(query, null, minScore, null, Float.POSITIVE_INFINITY);
-        FunctionScoreQuery fsq2 = new FunctionScoreQuery(new RandomApproximationQuery(query, random()), null, minScore, null,
-                Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq1 = new FunctionScoreQuery(query, minScore, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq2 = new FunctionScoreQuery(new RandomApproximationQuery(query, random()), minScore, Float.POSITIVE_INFINITY);
         assertSameScores(fsq1, fsq2);
 
-        FiltersFunctionScoreQuery ffsq1 = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0],
-                Float.POSITIVE_INFINITY, minScore, CombineFunction.MULTIPLY);
-        FiltersFunctionScoreQuery ffsq2 = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0],
-                Float.POSITIVE_INFINITY, minScore, CombineFunction.MULTIPLY);
+        FunctionScoreQuery ffsq1 = new FunctionScoreQuery(query, minScore, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery ffsq2 = new FunctionScoreQuery(query, minScore, Float.POSITIVE_INFINITY);
         assertSameScores(ffsq1, ffsq2);
     }
-
 }

+ 11 - 11
core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java

@@ -31,7 +31,6 @@ import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
 import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.WeightFactorFunction;
 import org.elasticsearch.common.unit.DistanceUnit;
@@ -99,7 +98,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
             functionScoreQueryBuilder.boostMode(randomFrom(CombineFunction.values()));
         }
         if (randomBoolean()) {
-            functionScoreQueryBuilder.scoreMode(randomFrom(FiltersFunctionScoreQuery.ScoreMode.values()));
+            functionScoreQueryBuilder.scoreMode(randomFrom(FunctionScoreQuery.ScoreMode.values()));
         }
         if (randomBoolean()) {
             functionScoreQueryBuilder.maxBoost(randomFloat());
@@ -254,7 +253,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
 
     @Override
     protected void doAssertLuceneQuery(FunctionScoreQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException {
-        assertThat(query, either(instanceOf(FunctionScoreQuery.class)).or(instanceOf(FiltersFunctionScoreQuery.class)));
+        assertThat(query, either(instanceOf(FunctionScoreQuery.class)).or(instanceOf(FunctionScoreQuery.class)));
     }
 
     /**
@@ -367,7 +366,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
                     .filterFunctionBuilders()[2].getScoreFunction();
             assertThat(gaussDecayFunctionBuilder.getFieldName(), equalTo("field_name"));
             assertThat(functionScoreQueryBuilder.boost(), equalTo(3f));
-            assertThat(functionScoreQueryBuilder.scoreMode(), equalTo(FiltersFunctionScoreQuery.ScoreMode.AVG));
+            assertThat(functionScoreQueryBuilder.scoreMode(), equalTo(FunctionScoreQuery.ScoreMode.AVG));
             assertThat(functionScoreQueryBuilder.boostMode(), equalTo(CombineFunction.REPLACE));
             assertThat(functionScoreQueryBuilder.maxBoost(), equalTo(10f));
 
@@ -422,7 +421,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
             assertThat(gaussDecayFunctionBuilder.getFieldName(), equalTo("field_name"));
             assertThat(gaussDecayFunctionBuilder.getWeight(), nullValue());
             assertThat(functionScoreQueryBuilder.boost(), equalTo(3f));
-            assertThat(functionScoreQueryBuilder.scoreMode(), equalTo(FiltersFunctionScoreQuery.ScoreMode.AVG));
+            assertThat(functionScoreQueryBuilder.scoreMode(), equalTo(FunctionScoreQuery.ScoreMode.AVG));
             assertThat(functionScoreQueryBuilder.boostMode(), equalTo(CombineFunction.REPLACE));
             assertThat(functionScoreQueryBuilder.maxBoost(), equalTo(10f));
 
@@ -523,8 +522,9 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
         Query luceneQuery = query.toQuery(createShardContext());
         assertThat(luceneQuery, instanceOf(FunctionScoreQuery.class));
         FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) luceneQuery;
-        assertThat(functionScoreQuery.getFunction(), instanceOf(WeightFactorFunction.class));
-        WeightFactorFunction weightFactorFunction = (WeightFactorFunction) functionScoreQuery.getFunction();
+        assertThat(functionScoreQuery.getFunctions().length, equalTo(1));
+        assertThat(functionScoreQuery.getFunctions()[0], instanceOf(WeightFactorFunction.class));
+        WeightFactorFunction weightFactorFunction = (WeightFactorFunction) functionScoreQuery.getFunctions()[0];
         assertThat(weightFactorFunction.getWeight(), equalTo(1.0f));
         assertThat(weightFactorFunction.getScoreFunction(), instanceOf(FieldValueFactorFunction.class));
     }
@@ -573,7 +573,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
         assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
         FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
         assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term("name.last", "banon")));
-        assertThat((double) ((WeightFactorFunction) functionScoreQuery.getFunction()).getWeight(), closeTo(1.3, 0.001));
+        assertThat((double) (functionScoreQuery.getFunctions()[0]).getWeight(), closeTo(1.3, 0.001));
     }
 
     public void testCustomWeightFactorQueryBuilderWithFunctionScoreWithoutQueryGiven() throws IOException {
@@ -581,7 +581,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
         assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
         FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
         assertThat(functionScoreQuery.getSubQuery() instanceof MatchAllDocsQuery, equalTo(true));
-        assertThat((double) ((WeightFactorFunction) functionScoreQuery.getFunction()).getWeight(), closeTo(1.3, 0.001));
+        assertThat((double) (functionScoreQuery.getFunctions()[0]).getWeight(), closeTo(1.3, 0.001));
     }
 
     public void testFieldValueFactorFactorArray() throws IOException {
@@ -666,14 +666,14 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase<Functi
         FunctionScoreQueryBuilder functionScoreQueryBuilder =
             new FunctionScoreQueryBuilder(new WrapperQueryBuilder(new TermQueryBuilder("foo", "bar").toString()))
                 .boostMode(CombineFunction.REPLACE)
-                .scoreMode(FiltersFunctionScoreQuery.ScoreMode.SUM)
+                .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                 .setMinScore(1)
                 .maxBoost(100);
         FunctionScoreQueryBuilder rewrite = (FunctionScoreQueryBuilder) functionScoreQueryBuilder.rewrite(createShardContext());
         assertNotSame(functionScoreQueryBuilder, rewrite);
         assertEquals(rewrite.query(), new TermQueryBuilder("foo", "bar"));
         assertEquals(rewrite.boostMode(), CombineFunction.REPLACE);
-        assertEquals(rewrite.scoreMode(), FiltersFunctionScoreQuery.ScoreMode.SUM);
+        assertEquals(rewrite.scoreMode(), FunctionScoreQuery.ScoreMode.SUM);
         assertEquals(rewrite.getMinScore(), 1f, 0.0001);
         assertEquals(rewrite.maxBoost(), 100f, 0.0001);
     }

+ 128 - 97
core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreTests.java

@@ -41,13 +41,12 @@ import org.apache.lucene.search.SortField;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
 import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.FilterFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery.ScoreMode;
 import org.elasticsearch.common.lucene.search.function.LeafScoreFunction;
 import org.elasticsearch.common.lucene.search.function.RandomScoreFunction;
 import org.elasticsearch.common.lucene.search.function.ScoreFunction;
@@ -69,8 +68,10 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.elasticsearch.common.lucene.search.function.FunctionScoreQuery.FilterScoreFunction;
 
 public class FunctionScoreTests extends ESTestCase {
 
@@ -318,7 +319,7 @@ public class FunctionScoreTests extends ESTestCase {
     }
 
     public Explanation getFunctionScoreExplanation(IndexSearcher searcher, ScoreFunction scoreFunction) throws IOException {
-        FunctionScoreQuery functionScoreQuery = new FunctionScoreQuery(new TermQuery(TERM), scoreFunction, 0.0f, CombineFunction.AVG, 100);
+        FunctionScoreQuery functionScoreQuery = new FunctionScoreQuery(new TermQuery(TERM), scoreFunction, CombineFunction.AVG,0.0f, 100);
         Weight weight = searcher.createNormalizedWeight(functionScoreQuery, true);
         Explanation explanation = weight.explain(searcher.getIndexReader().leaves().get(0), 0);
         return explanation.getDetails()[1];
@@ -326,7 +327,7 @@ public class FunctionScoreTests extends ESTestCase {
 
     public void checkFunctionScoreExplanation(Explanation randomExplanation, String functionExpl) {
         assertThat(randomExplanation.getDescription(), equalTo("min of:"));
-        assertThat(randomExplanation.getDetails()[0].getDescription(), equalTo(functionExpl));
+        assertThat(randomExplanation.getDetails()[0].getDescription(), containsString(functionExpl));
     }
 
     public void testExplainFiltersFunctionScoreQuery() throws IOException {
@@ -390,25 +391,24 @@ public class FunctionScoreTests extends ESTestCase {
     }
 
     public Explanation getFiltersFunctionScoreExplanation(IndexSearcher searcher, ScoreFunction... scoreFunctions) throws IOException {
-        FiltersFunctionScoreQuery filtersFunctionScoreQuery = getFiltersFunctionScoreQuery(FiltersFunctionScoreQuery.ScoreMode.AVG,
+        FunctionScoreQuery functionScoreQuery = getFiltersFunctionScoreQuery(FunctionScoreQuery.ScoreMode.AVG,
                 CombineFunction.AVG, scoreFunctions);
-        return getExplanation(searcher, filtersFunctionScoreQuery).getDetails()[1];
+        return getExplanation(searcher, functionScoreQuery).getDetails()[1];
     }
 
-    protected Explanation getExplanation(IndexSearcher searcher, FiltersFunctionScoreQuery filtersFunctionScoreQuery) throws IOException {
-        Weight weight = searcher.createNormalizedWeight(filtersFunctionScoreQuery, true);
+    protected Explanation getExplanation(IndexSearcher searcher, FunctionScoreQuery functionScoreQuery) throws IOException {
+        Weight weight = searcher.createNormalizedWeight(functionScoreQuery, true);
         return weight.explain(searcher.getIndexReader().leaves().get(0), 0);
     }
 
-    public FiltersFunctionScoreQuery getFiltersFunctionScoreQuery(FiltersFunctionScoreQuery.ScoreMode scoreMode,
-            CombineFunction combineFunction, ScoreFunction... scoreFunctions) {
-        FilterFunction[] filterFunctions = new FilterFunction[scoreFunctions.length];
+    public FunctionScoreQuery getFiltersFunctionScoreQuery(FunctionScoreQuery.ScoreMode scoreMode,
+                                                           CombineFunction combineFunction, ScoreFunction... scoreFunctions) {
+        ScoreFunction[] filterFunctions = new ScoreFunction[scoreFunctions.length];
         for (int i = 0; i < scoreFunctions.length; i++) {
-            filterFunctions[i] = new FiltersFunctionScoreQuery.FilterFunction(
+            filterFunctions[i] = new FunctionScoreQuery.FilterScoreFunction(
                 new TermQuery(TERM), scoreFunctions[i]);
         }
-        return new FiltersFunctionScoreQuery(new TermQuery(TERM), scoreMode, filterFunctions, Float.MAX_VALUE, Float.MAX_VALUE * -1,
-                combineFunction);
+        return new FunctionScoreQuery(new TermQuery(TERM), scoreMode, filterFunctions, combineFunction,Float.MAX_VALUE * -1, Float.MAX_VALUE);
     }
 
     public void checkFiltersFunctionScoreExplanation(Explanation randomExplanation, String functionExpl, int whichFunction) {
@@ -489,45 +489,45 @@ public class FunctionScoreTests extends ESTestCase {
             weightFunctionStubs[i] = new WeightFactorFunction(weights[i], scoreFunctionStubs[i]);
         }
 
-        FiltersFunctionScoreQuery filtersFunctionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
-                FiltersFunctionScoreQuery.ScoreMode.MULTIPLY
+        FunctionScoreQuery functionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
+                FunctionScoreQuery.ScoreMode.MULTIPLY
                 , CombineFunction.REPLACE
                 , weightFunctionStubs
         );
 
-        TopDocs topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
+        TopDocs topDocsWithWeights = searcher.search(functionScoreQueryWithWeights, 1);
         float scoreWithWeight = topDocsWithWeights.scoreDocs[0].score;
         double score = 1;
         for (int i = 0; i < weights.length; i++) {
             score *= weights[i] * scores[i];
         }
         assertThat(scoreWithWeight / (float) score, is(1f));
-        float explainedScore = getExplanation(searcher, filtersFunctionScoreQueryWithWeights).getValue();
+        float explainedScore = getExplanation(searcher, functionScoreQueryWithWeights).getValue();
         assertThat(explainedScore / scoreWithWeight, is(1f));
 
-        filtersFunctionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
-                FiltersFunctionScoreQuery.ScoreMode.SUM
+        functionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
+                FunctionScoreQuery.ScoreMode.SUM
                 , CombineFunction.REPLACE
                 , weightFunctionStubs
         );
 
-        topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
+        topDocsWithWeights = searcher.search(functionScoreQueryWithWeights, 1);
         scoreWithWeight = topDocsWithWeights.scoreDocs[0].score;
         double sum = 0;
         for (int i = 0; i < weights.length; i++) {
             sum += weights[i] * scores[i];
         }
         assertThat(scoreWithWeight / (float) sum, is(1f));
-        explainedScore = getExplanation(searcher, filtersFunctionScoreQueryWithWeights).getValue();
+        explainedScore = getExplanation(searcher, functionScoreQueryWithWeights).getValue();
         assertThat(explainedScore / scoreWithWeight, is(1f));
 
-        filtersFunctionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
-                FiltersFunctionScoreQuery.ScoreMode.AVG
+        functionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
+                FunctionScoreQuery.ScoreMode.AVG
                 , CombineFunction.REPLACE
                 , weightFunctionStubs
         );
 
-        topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
+        topDocsWithWeights = searcher.search(functionScoreQueryWithWeights, 1);
         scoreWithWeight = topDocsWithWeights.scoreDocs[0].score;
         double norm = 0;
         sum = 0;
@@ -536,45 +536,45 @@ public class FunctionScoreTests extends ESTestCase {
             sum += weights[i] * scores[i];
         }
         assertThat(scoreWithWeight / (float) (sum / norm), is(1f));
-        explainedScore = getExplanation(searcher, filtersFunctionScoreQueryWithWeights).getValue();
+        explainedScore = getExplanation(searcher, functionScoreQueryWithWeights).getValue();
         assertThat(explainedScore / scoreWithWeight, is(1f));
 
-        filtersFunctionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
-                FiltersFunctionScoreQuery.ScoreMode.MIN
+        functionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
+                FunctionScoreQuery.ScoreMode.MIN
                 , CombineFunction.REPLACE
                 , weightFunctionStubs
         );
 
-        topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
+        topDocsWithWeights = searcher.search(functionScoreQueryWithWeights, 1);
         scoreWithWeight = topDocsWithWeights.scoreDocs[0].score;
         double min = Double.POSITIVE_INFINITY;
         for (int i = 0; i < weights.length; i++) {
             min = Math.min(min, weights[i] * scores[i]);
         }
         assertThat(scoreWithWeight / (float) min, is(1f));
-        explainedScore = getExplanation(searcher, filtersFunctionScoreQueryWithWeights).getValue();
+        explainedScore = getExplanation(searcher, functionScoreQueryWithWeights).getValue();
         assertThat(explainedScore / scoreWithWeight, is(1f));
 
-        filtersFunctionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
-                FiltersFunctionScoreQuery.ScoreMode.MAX
+        functionScoreQueryWithWeights = getFiltersFunctionScoreQuery(
+                FunctionScoreQuery.ScoreMode.MAX
                 , CombineFunction.REPLACE
                 , weightFunctionStubs
         );
 
-        topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
+        topDocsWithWeights = searcher.search(functionScoreQueryWithWeights, 1);
         scoreWithWeight = topDocsWithWeights.scoreDocs[0].score;
         double max = Double.NEGATIVE_INFINITY;
         for (int i = 0; i < weights.length; i++) {
             max = Math.max(max, weights[i] * scores[i]);
         }
         assertThat(scoreWithWeight / (float) max, is(1f));
-        explainedScore = getExplanation(searcher, filtersFunctionScoreQueryWithWeights).getValue();
+        explainedScore = getExplanation(searcher, functionScoreQueryWithWeights).getValue();
         assertThat(explainedScore / scoreWithWeight, is(1f));
     }
 
     public void testWeightOnlyCreatesBoostFunction() throws IOException {
         FunctionScoreQuery filtersFunctionScoreQueryWithWeights = new FunctionScoreQuery(new MatchAllDocsQuery(),
-                new WeightFactorFunction(2), 0.0f, CombineFunction.MULTIPLY, 100);
+            new WeightFactorFunction(2), CombineFunction.MULTIPLY,0.0f, 100);
         TopDocs topDocsWithWeights = searcher.search(filtersFunctionScoreQueryWithWeights, 1);
         float score = topDocsWithWeights.scoreDocs[0].score;
         assertThat(score, equalTo(2.0f));
@@ -584,26 +584,24 @@ public class FunctionScoreTests extends ESTestCase {
         Query query = new MatchAllDocsQuery();
         Explanation queryExpl = searcher.explain(query, 0);
 
-        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, 0f, null, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq = new FunctionScoreQuery(query,0f, Float.POSITIVE_INFINITY);
         Explanation fsqExpl = searcher.explain(fsq, 0);
         assertTrue(fsqExpl.isMatch());
         assertEquals(queryExpl.getValue(), fsqExpl.getValue(), 0f);
         assertEquals(queryExpl.getDescription(), fsqExpl.getDescription());
 
-        fsq = new FunctionScoreQuery(query, null, 10f, null, Float.POSITIVE_INFINITY);
+        fsq = new FunctionScoreQuery(query, 10f, Float.POSITIVE_INFINITY);
         fsqExpl = searcher.explain(fsq, 0);
         assertFalse(fsqExpl.isMatch());
         assertEquals("Score value is too low, expected at least 10.0 but got 1.0", fsqExpl.getDescription());
 
-        FiltersFunctionScoreQuery ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY,
-                0f, CombineFunction.MULTIPLY);
+        FunctionScoreQuery ffsq = new FunctionScoreQuery(query, 0f, Float.POSITIVE_INFINITY);
         Explanation ffsqExpl = searcher.explain(ffsq, 0);
         assertTrue(ffsqExpl.isMatch());
         assertEquals(queryExpl.getValue(), ffsqExpl.getValue(), 0f);
-        assertEquals(queryExpl.getDescription(), ffsqExpl.getDetails()[0].getDescription());
+        assertEquals(queryExpl.getDescription(), ffsqExpl.getDescription());
 
-        ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY, 10f,
-                CombineFunction.MULTIPLY);
+        ffsq = new FunctionScoreQuery(query, 10f, Float.POSITIVE_INFINITY);
         ffsqExpl = searcher.explain(ffsq, 0);
         assertFalse(ffsqExpl.isMatch());
         assertEquals("Score value is too low, expected at least 10.0 but got 1.0", ffsqExpl.getDescription());
@@ -614,44 +612,33 @@ public class FunctionScoreTests extends ESTestCase {
         IndexSearcher searcher = newSearcher(reader);
         searcher.setQueryCache(null); // otherwise we could get a cached entry that does not have approximations
 
-        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, null, null, Float.POSITIVE_INFINITY);
+        FunctionScoreQuery fsq = new FunctionScoreQuery(query, null, Float.POSITIVE_INFINITY);
         for (boolean needsScores : new boolean[] {true, false}) {
             Weight weight = searcher.createWeight(fsq, needsScores, 1f);
             Scorer scorer = weight.scorer(reader.leaves().get(0));
             assertNotNull(scorer.twoPhaseIterator());
         }
-
-        FiltersFunctionScoreQuery ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY,
-                null, CombineFunction.MULTIPLY);
-        for (boolean needsScores : new boolean[] {true, false}) {
-            Weight weight = searcher.createWeight(ffsq, needsScores, 1f);
-            Scorer scorer = weight.scorer(reader.leaves().get(0));
-            assertNotNull(scorer.twoPhaseIterator());
-        }
     }
 
     public void testFunctionScoreHashCodeAndEquals() {
         Float minScore = randomBoolean() ? null : 1.0f;
         CombineFunction combineFunction = randomFrom(CombineFunction.values());
         float maxBoost = randomBoolean() ? Float.POSITIVE_INFINITY : randomFloat();
-        ScoreFunction function = randomBoolean() ? null : new DummyScoreFunction(combineFunction);
+        ScoreFunction function = new DummyScoreFunction(combineFunction);
 
-        FunctionScoreQuery q = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, minScore, combineFunction, maxBoost);
-        FunctionScoreQuery q1 = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, minScore, combineFunction,
-                maxBoost);
+        FunctionScoreQuery q = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, combineFunction, minScore, maxBoost);
+        FunctionScoreQuery q1 = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, combineFunction, minScore, maxBoost);
         assertEquals(q, q);
         assertEquals(q.hashCode(), q.hashCode());
         assertEquals(q, q1);
         assertEquals(q.hashCode(), q1.hashCode());
 
-        FunctionScoreQuery diffQuery = new FunctionScoreQuery(new TermQuery(new Term("foo", "baz")), function, minScore, combineFunction,
-                maxBoost);
-        FunctionScoreQuery diffMinScore = new FunctionScoreQuery(q.getSubQuery(), function, minScore == null ? 1.0f : null, combineFunction,
-                maxBoost);
-        ScoreFunction otherFunciton = function == null ? new DummyScoreFunction(combineFunction) : null;
-        FunctionScoreQuery diffFunction = new FunctionScoreQuery(q.getSubQuery(), otherFunciton, minScore, combineFunction, maxBoost);
-        FunctionScoreQuery diffMaxBoost = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, minScore, combineFunction,
-                maxBoost == 1.0f ? 0.9f : 1.0f);
+        FunctionScoreQuery diffQuery = new FunctionScoreQuery(new TermQuery(new Term("foo", "baz")), function, combineFunction, minScore, maxBoost);
+        FunctionScoreQuery diffMinScore = new FunctionScoreQuery(q.getSubQuery(), function, combineFunction, minScore == null ? 1.0f : null, maxBoost);
+        ScoreFunction otherFunction = new DummyScoreFunction(combineFunction);
+        FunctionScoreQuery diffFunction = new FunctionScoreQuery(q.getSubQuery(), otherFunction, combineFunction, minScore, maxBoost);
+        FunctionScoreQuery diffMaxBoost = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")),
+            function, combineFunction, minScore, maxBoost == 1.0f ? 0.9f : 1.0f);
         FunctionScoreQuery[] queries = new FunctionScoreQuery[] { diffFunction,
             diffMinScore,
             diffQuery,
@@ -673,51 +660,43 @@ public class FunctionScoreTests extends ESTestCase {
     }
 
     public void testFilterFunctionScoreHashCodeAndEquals() {
-        ScoreMode mode = randomFrom(ScoreMode.values());
         CombineFunction combineFunction = randomFrom(CombineFunction.values());
         ScoreFunction scoreFunction = new DummyScoreFunction(combineFunction);
         Float minScore = randomBoolean() ? null : 1.0f;
         Float maxBoost = randomBoolean() ? Float.POSITIVE_INFINITY : randomFloat();
 
-        FilterFunction function = new FilterFunction(new TermQuery(new Term("filter", "query")), scoreFunction);
-        FiltersFunctionScoreQuery q = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                new FilterFunction[] { function }, maxBoost, minScore, combineFunction);
-        FiltersFunctionScoreQuery q1 = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                new FilterFunction[] { function }, maxBoost, minScore, combineFunction);
+        FilterScoreFunction function = new FilterScoreFunction(new TermQuery(new Term("filter", "query")), scoreFunction);
+        FunctionScoreQuery q = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")),
+                function, combineFunction, minScore, maxBoost);
+        FunctionScoreQuery q1 = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function, combineFunction, minScore, maxBoost);
         assertEquals(q, q);
         assertEquals(q.hashCode(), q.hashCode());
         assertEquals(q, q1);
         assertEquals(q.hashCode(), q1.hashCode());
-        FiltersFunctionScoreQuery diffCombineFunc = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                new FilterFunction[] { function }, maxBoost, minScore,
-                combineFunction == CombineFunction.AVG ? CombineFunction.MAX : CombineFunction.AVG);
-        FiltersFunctionScoreQuery diffQuery = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "baz")), mode,
-                new FilterFunction[] { function }, maxBoost, minScore, combineFunction);
-        FiltersFunctionScoreQuery diffMode = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")),
-                mode == ScoreMode.AVG ? ScoreMode.FIRST : ScoreMode.AVG, new FilterFunction[] { function }, maxBoost, minScore,
-                combineFunction);
-        FiltersFunctionScoreQuery diffMaxBoost = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                new FilterFunction[] { function }, maxBoost == 1.0f ? 0.9f : 1.0f, minScore, combineFunction);
-        FiltersFunctionScoreQuery diffMinScore = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                new FilterFunction[] { function }, maxBoost, minScore == null ? 0.9f : null, combineFunction);
-        FilterFunction otherFunc = new FilterFunction(new TermQuery(new Term("filter", "other_query")), scoreFunction);
-        FiltersFunctionScoreQuery diffFunc = new FiltersFunctionScoreQuery(new TermQuery(new Term("foo", "bar")), mode,
-                randomBoolean() ? new FilterFunction[] { function, otherFunc } : new FilterFunction[] { otherFunc }, maxBoost, minScore,
-                combineFunction);
-
-        FiltersFunctionScoreQuery[] queries = new FiltersFunctionScoreQuery[] {
+        FunctionScoreQuery diffCombineFunc = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), function,
+                combineFunction == CombineFunction.AVG ? CombineFunction.MAX : CombineFunction.AVG, minScore, maxBoost);
+        FunctionScoreQuery diffQuery = new FunctionScoreQuery(new TermQuery(new Term("foo", "baz")),
+                function, combineFunction, minScore, maxBoost);
+        FunctionScoreQuery diffMaxBoost = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")),
+                function, combineFunction, minScore, maxBoost == 1.0f ? 0.9f : 1.0f);
+        FunctionScoreQuery diffMinScore = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")),
+            function, combineFunction, minScore == null ? 0.9f : null, maxBoost);
+        FilterScoreFunction otherFunc = new FilterScoreFunction(new TermQuery(new Term("filter", "other_query")), scoreFunction);
+        FunctionScoreQuery diffFunc = new FunctionScoreQuery(new TermQuery(new Term("foo", "bar")), randomFrom(ScoreMode.values()),
+            randomBoolean() ? new ScoreFunction[] { function, otherFunc } : new ScoreFunction[] { otherFunc }, combineFunction, minScore, maxBoost);
+
+        FunctionScoreQuery[] queries = new FunctionScoreQuery[] {
             diffQuery,
             diffMaxBoost,
             diffMinScore,
-            diffMode,
             diffFunc,
             q,
             diffCombineFunc
         };
         final int numIters = randomIntBetween(20, 100);
         for (int i = 0; i < numIters; i++) {
-            FiltersFunctionScoreQuery left = randomFrom(queries);
-            FiltersFunctionScoreQuery right = randomFrom(queries);
+            FunctionScoreQuery left = randomFrom(queries);
+            FunctionScoreQuery right = randomFrom(queries);
             if (left == right) {
                 assertEquals(left, right);
                 assertEquals(left.hashCode(), right.hashCode());
@@ -736,17 +715,17 @@ public class FunctionScoreTests extends ESTestCase {
             CombineFunction.MULTIPLY, CombineFunction.REPLACE});
 
         // check for document that has no macthing function
-        FiltersFunctionScoreQuery query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
-            new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "2")), new WeightFactorFunction(10))},
-            Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
+        FunctionScoreQuery query = new FunctionScoreQuery(new TermQuery(new Term(FIELD, "out")),
+            new FilterScoreFunction(new TermQuery(new Term("_uid", "2")), new WeightFactorFunction(10)),
+            combineFunction, Float.NEGATIVE_INFINITY, Float.MAX_VALUE);
         TopDocs searchResult = localSearcher.search(query, 1);
         Explanation explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
         assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
 
         // check for document that has a matching function
-        query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
-            new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "1")), new WeightFactorFunction(10))},
-            Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
+        query = new FunctionScoreQuery(new TermQuery(new Term(FIELD, "out")),
+            new FilterScoreFunction(new TermQuery(new Term("_uid", "1")), new WeightFactorFunction(10)),
+                combineFunction, Float.NEGATIVE_INFINITY, Float.MAX_VALUE);
         searchResult = localSearcher.search(query, 1);
         explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
         assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
@@ -779,6 +758,58 @@ public class FunctionScoreTests extends ESTestCase {
         }
     }
 
+
+    private static class ConstantScoreFunction extends ScoreFunction {
+        final double value;
+
+        protected ConstantScoreFunction(double value) {
+            super(CombineFunction.REPLACE);
+            this.value = value;
+        }
+
+        @Override
+        public LeafScoreFunction getLeafScoreFunction(LeafReaderContext ctx) throws IOException {
+            return new LeafScoreFunction() {
+                @Override
+                public double score(int docId, float subQueryScore) throws IOException {
+                    return value;
+                }
+
+                @Override
+                public Explanation explainScore(int docId, Explanation subQueryScore) throws IOException {
+                    return null;
+                }
+            };
+        }
+
+        @Override
+        public boolean needsScores() {
+            return false;
+        }
+
+        @Override
+        protected boolean doEquals(ScoreFunction other) {
+            return false;
+        }
+
+        @Override
+        protected int doHashCode() {
+            return 0;
+        }
+    }
+
+    public void testWithInvalidScores() {
+        IndexSearcher localSearcher = newSearcher(reader);
+        FunctionScoreQuery query1 = new FunctionScoreQuery(new TermQuery(new Term(FIELD, "out")),
+            new ConstantScoreFunction(Float.NaN), CombineFunction.REPLACE, null, Float.POSITIVE_INFINITY);
+        ElasticsearchException exc = expectThrows(ElasticsearchException.class, () -> localSearcher.search(query1, 1));
+        assertThat(exc.getMessage(), containsString("function score query returned an invalid score: " + Float.NaN));
+        FunctionScoreQuery query2 = new FunctionScoreQuery(new TermQuery(new Term(FIELD, "out")),
+            new ConstantScoreFunction(Float.NEGATIVE_INFINITY), CombineFunction.REPLACE, null, Float.POSITIVE_INFINITY);
+        exc = expectThrows(ElasticsearchException.class, () -> localSearcher.search(query2, 1));
+        assertThat(exc.getMessage(), containsString("function score query returned an invalid score: " + Float.NEGATIVE_INFINITY));
+    }
+
     private static class DummyScoreFunction extends ScoreFunction {
         protected DummyScoreFunction(CombineFunction scoreCombiner) {
             super(scoreCombiner);

+ 6 - 7
core/src/test/java/org/elasticsearch/search/functionscore/DecayFunctionScoreIT.java

@@ -28,8 +28,8 @@ import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery.ScoreMode;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
@@ -71,7 +71,6 @@ import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.isOneOf;
 import static org.hamcrest.Matchers.lessThan;
 
 public class DecayFunctionScoreIT extends ESIntegTestCase {
@@ -546,7 +545,7 @@ public class DecayFunctionScoreIT extends ESIntegTestCase {
                                 functionScoreQuery(baseQuery, new FilterFunctionBuilder[]{
                                         new FilterFunctionBuilder(linearDecayFunction("num1", "2013-05-28", "+3d")),
                                         new FilterFunctionBuilder(linearDecayFunction("num2", "0.0", "1"))
-                                }).scoreMode(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY))));
+                                }).scoreMode(FunctionScoreQuery.ScoreMode.MULTIPLY))));
 
         SearchResponse sr = response.actionGet();
 
@@ -598,7 +597,7 @@ public class DecayFunctionScoreIT extends ESIntegTestCase {
                                         new FilterFunctionBuilder(linearDecayFunction("num1", null, "7000d")),
                                         new FilterFunctionBuilder(gaussDecayFunction("num1", null, "1d")),
                                         new FilterFunctionBuilder(exponentialDecayFunction("num1", null, "7000d"))
-                                }).scoreMode(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY))));
+                                }).scoreMode(FunctionScoreQuery.ScoreMode.MULTIPLY))));
 
         SearchResponse sr = response.actionGet();
         assertNoFailures(sr);
@@ -686,7 +685,7 @@ public class DecayFunctionScoreIT extends ESIntegTestCase {
                         searchSource()
                                 .size(numDocs)
                                 .query(functionScoreQuery(termQuery("test", "value"), linearDecayFunction("type.geo", lonlat, "1000km"))
-                                        .scoreMode(FiltersFunctionScoreQuery.ScoreMode.MULTIPLY))));
+                                        .scoreMode(FunctionScoreQuery.ScoreMode.MULTIPLY))));
         try {
             response.actionGet();
             fail("Expected SearchPhaseExecutionException");
@@ -730,7 +729,7 @@ public class DecayFunctionScoreIT extends ESIntegTestCase {
                 searchRequest().searchType(SearchType.QUERY_THEN_FETCH).source(
                         searchSource().query(
                                 functionScoreQuery(linearDecayFunction("num", 1, 0.5)).scoreMode(
-                                        FiltersFunctionScoreQuery.ScoreMode.MULTIPLY))));
+                                        FunctionScoreQuery.ScoreMode.MULTIPLY))));
         response.actionGet();
     }
 

+ 4 - 1
core/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreFieldValueIT.java

@@ -113,7 +113,10 @@ public class FunctionScoreFieldValueIT extends ESIntegTestCase {
         assertEquals(response.getHits().getAt(0).getScore(), response.getHits().getAt(2).getScore(), 0);
 
 
-        // n divided by 0 is infinity, which should provoke an exception.
+        client().prepareIndex("test", "type1", "2").setSource("test", -1, "body", "foo").get();
+        refresh();
+
+        // -1 divided by 0 is infinity, which should provoke an exception.
         try {
             response = client().prepareSearch("test")
                     .setExplain(randomBoolean())

+ 3 - 3
core/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreIT.java

@@ -22,7 +22,7 @@ package org.elasticsearch.search.functionscore;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.index.fielddata.ScriptDocValues;
 import org.elasticsearch.index.query.MatchAllQueryBuilder;
 import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
@@ -160,7 +160,7 @@ public class FunctionScoreIT extends ESIntegTestCase {
                 searchRequest().source(searchSource().query(functionScoreQuery(new MatchAllQueryBuilder(), new FilterFunctionBuilder[] {
                                 new FilterFunctionBuilder(scriptFunction(script)),
                                 new FilterFunctionBuilder(scriptFunction(script))
-                        }).scoreMode(FiltersFunctionScoreQuery.ScoreMode.AVG).setMinScore(minScore)))
+                        }).scoreMode(FunctionScoreQuery.ScoreMode.AVG).setMinScore(minScore)))
                 ).actionGet();
         if (score < minScore) {
             assertThat(searchResponse.getHits().getTotalHits(), is(0L));
@@ -196,7 +196,7 @@ public class FunctionScoreIT extends ESIntegTestCase {
                 searchRequest().source(searchSource().query(functionScoreQuery(new MatchAllQueryBuilder(), new FilterFunctionBuilder[] {
                         new FilterFunctionBuilder(scriptFunction(script)),
                         new FilterFunctionBuilder(scriptFunction(script))
-                }).scoreMode(FiltersFunctionScoreQuery.ScoreMode.AVG).setMinScore(minScore)).size(numDocs))).actionGet();
+                }).scoreMode(FunctionScoreQuery.ScoreMode.AVG).setMinScore(minScore)).size(numDocs))).actionGet();
         assertMinScoreSearchResponses(numDocs, searchResponse, numMatchingDocs);
     }
 

+ 2 - 2
modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java

@@ -26,7 +26,7 @@ import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
 import org.elasticsearch.common.lucene.search.function.CombineFunction;
-import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
+import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.index.query.BoolQueryBuilder;
@@ -1676,7 +1676,7 @@ public class ChildQuerySearchIT extends ParentChildTestCase {
                                     weightFactorFunction(1)),
                                 new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("foo", "four"),
                                     weightFactorFunction(1))
-                        }).boostMode(CombineFunction.REPLACE).scoreMode(FiltersFunctionScoreQuery.ScoreMode.SUM), scoreMode)
+                        }).boostMode(CombineFunction.REPLACE).scoreMode(FunctionScoreQuery.ScoreMode.SUM), scoreMode)
                 .minMaxChildren(minChildren, maxChildren != null ? maxChildren : HasChildQueryBuilder.DEFAULT_MAX_CHILDREN);
 
         return client()

+ 3 - 1
modules/percolator/src/test/java/org/elasticsearch/percolator/QueryAnalyzerTests.java

@@ -47,6 +47,7 @@ import org.apache.lucene.search.spans.SpanNotQuery;
 import org.apache.lucene.search.spans.SpanOrQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.lucene.search.function.CombineFunction;
 import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
 import org.elasticsearch.common.lucene.search.function.RandomScoreFunction;
 import org.elasticsearch.common.network.InetAddresses;
@@ -534,7 +535,8 @@ public class QueryAnalyzerTests extends ESTestCase {
         assertThat(result.verified, is(true));
         assertTermsEqual(result.extractions, new Term("_field", "_value"));
 
-        functionScoreQuery = new FunctionScoreQuery(termQuery, new RandomScoreFunction(0, 0, null), 1f, null, 10f);
+        functionScoreQuery = new FunctionScoreQuery(termQuery, new RandomScoreFunction(0, 0, null),
+            CombineFunction.MULTIPLY, 1f, 10f);
         result = analyze(functionScoreQuery);
         assertThat(result.verified, is(false));
         assertTermsEqual(result.extractions, new Term("_field", "_value"));