Skip to content

Function score queries with score_mode sum and a total weight of 0 always return a score of 1 #24910

@csben

Description

@csben

Elasticsearch version: 5.1.2 and 5.3.2

JVM version: 1.8.0_111 and 1.8.0_131

OS version: 4.4.0-75-generic and 2.6.32-642.13.1.el6.x86_64

Description of the problem including expected versus actual behavior:
Consider the following query:

curl -XPOST http://localhost:9200/test/_search -d '{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
        "functions": [
             {
                "filter": { "match": { "test_value": 1 } },
                "weight": 1
             },
             {
                "filter": { "match": { "test_value":1 } },
                "script_score": {
                  "script": "-1"
                }, 
                "weight": -1
             }
        ],
        "score_mode": "sum",
        "boost_mode": "replace"
      }
  }
}' | jq 

In principle, this should produce a score of 2 (with a contribution of 1 from each filter) for any event with test_value = 1. However, this query will always produce a score of 1. Any pair of functions with a weight of 1 and weight of -1 will produce a score of 1. Any set of functions where the sum of the weights is 0 will produce a score of 1.

I think the bug is in FiltersFunctionScoreQuery.java around line 311.

                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;

There is an if-statement checking if the weightSum is zero, presumably to avoid a divide by zero when computing the weighted average. However, since the same block of code is used to compute a straightforward weighted sum (not just a weighted average), this if-statement results in the method returning the default value for factor which is 1. I believe if we are computing a weighted sum, we should just set factor = totalFactor with no dependence on the value of weightSum.

Steps to reproduce:

curl -XPUT http://localhost:9200/test -d '{
  "mappings": {
    "test": {
      "properties": {
        "test_value": {
          "type": "integer"
        }
      }
    }
  }
}'

curl -XPOST http://localhost:9200/test/test -d '{
  "test_value": 1
}'

# Returns a score of 2.1 as expected
curl -XPOST http://localhost:9200/test/_search -d '{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
        "functions": [
             {
                "filter": { "match": { "test_value": 1 } },
                "weight": 1
             },
             {
                "filter": { "match": { "test_value":1 } },
                "script_score": {
                  "script": "-1"
                }, 
                "weight": -1.1
             }
        ],
        "score_mode": "sum",
        "boost_mode": "replace"
      }
  }
}' | jq 

# Returns a score of 1 instead of the expected score of 2
curl -XPOST http://localhost:9200/test/_search -d '{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
        "functions": [
             {
                "filter": { "match": { "test_value": 1 } },
                "weight": 1
             },
             {
                "filter": { "match": { "test_value":1 } },
                "script_score": {
                  "script": "-1"
                }, 
                "weight": -1
             }
        ],
        "score_mode": "sum",
        "boost_mode": "replace"
      }
  }
}' | jq 

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions