FunctionScoreQuery.CustomBoostFactorWeight.explain() dereferenced the scorer
returned by functionScorer(context) without a null check. functionScorer()
returns null when subQueryWeight.scorer(context) is null, so a sub-query whose
weight explains a document as a match while producing no scorer for that
segment made explain() throw a NullPointerException. Because explain() runs in
the fetch phase, one such clause failed the whole search request for requests
that succeed fine with explain disabled.
scorerSupplier() in the same weight returns null for the equivalent state,
which means "no matches on this segment". explain() now agrees with it and
returns Explanation.noMatch(...) wrapping the sub-query explanation instead of
dereferencing the null scorer.
The assert below the new guard is left alone. A sub-query that returns a scorer
which does not position on the document is the same class of disagreement, but
it changes the explanation for a different state and is better handled on its
own.
Signed-off-by: hyunwoo-kurly <hayden.kim@kurlycorp.com>
Description
FunctionScoreQuery.CustomBoostFactorWeight.explain()dereferenced the scorer fromfunctionScorer(context)without a null check:functionScorer()returnsnullwhensubQueryWeight.scorer(context)is null, so a sub-query whose weight explains a document as a match while producing no scorer for that segment madeexplain()throw aNullPointerException. Sinceexplain()runs in the fetch phase, one such clause failed the whole search request for requests that succeed fine withexplaindisabled.scorerSupplier()in the same weight returnsnullfor the equivalent state, which means "no matches on this segment". This change makesexplain()agree with it: a null scorer now yieldsExplanation.noMatch(...)wrapping the sub-query explanation instead of dereferencing the null scorer.The
assert (actualDoc == doc)below the new guard is deliberately left alone. A sub-query that returns a scorer which does not position on the document is the same class of disagreement, but handling it changes the explanation for a different state and is better done as its own change once this one lands.Testing:
FunctionScoreTests#testExplainFunctionScoreQueryWhenSubQueryHasNoScorercovers the scorer/explain mismatch with a sub-query whose weight always explains a match and never returns a scorer. Reverting the guard makes it fail with theNullPointerExceptiondescribed above../gradlew :server:test --tests "org.opensearch.index.query.functionscore.FunctionScoreTests"passes.The CHANGELOG is not updated, since as of 3.6 it is no longer used to generate release notes (#21071).
Related Issues
Resolves #22634
Same defect class as #18446, fixed by #19650 in
scorerSupplier(), and #22619, which #22624 addresses inScriptScoreQuery.explain().FunctionScoreQuery.explain()is the remaining site, which neither change touches, so this PR does not overlap with #22624.Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.