diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e1b768..d8b86560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Removed redundant dependency on mock and upgrade Python syntax ([#785](https://github.com/opensearch-project/opensearch-py/pull/785)) ### Fixed - Fixed Search helper to ensure proper retention of the _collapse attribute in chained operations. ([#771](https://github.com/opensearch-project/opensearch-py/pull/771)) +- Fixed the use of `minimum_should_match` with `Bool` to allow the use of string-based value (percent string, combination). ([#780](https://github.com/opensearch-project/opensearch-py/pull/780)) ### Updated APIs - Updated opensearch-py APIs to reflect [opensearch-api-specification@0453dbe](https://github.com/opensearch-project/opensearch-api-specification/commit/0453dbe35080bf31eebac1d75f8022667d019e9f) - Updated opensearch-py APIs to reflect [opensearch-api-specification@0b033a9](https://github.com/opensearch-project/opensearch-api-specification/commit/0b033a92cac4cb20ec3fb51350c139afc753b089) diff --git a/opensearchpy/helpers/query.py b/opensearchpy/helpers/query.py index c7878f60..0da2eeb3 100644 --- a/opensearchpy/helpers/query.py +++ b/opensearchpy/helpers/query.py @@ -219,10 +219,12 @@ class Bool(Query): del q._params["minimum_should_match"] for qx in (self, other): - # TODO: percentages will fail here min_should_match = qx._min_should_match # all subqueries are required - if len(qx.should) <= min_should_match: + if ( + isinstance(min_should_match, int) + and len(qx.should) <= min_should_match + ): q.must.extend(qx.should) # not all of them are required, use it and remember min_should_match elif not q.should: diff --git a/test_opensearchpy/test_helpers/test_query.py b/test_opensearchpy/test_helpers/test_query.py index 7fb2f3a0..3acde60b 100644 --- a/test_opensearchpy/test_helpers/test_query.py +++ b/test_opensearchpy/test_helpers/test_query.py @@ -23,9 +23,9 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - from typing import Any +import pytest from pytest import raises from opensearchpy.helpers import function, query @@ -564,3 +564,58 @@ def test_script_score() -> None: assert isinstance(q.query, query.MatchAll) assert q.script == {"source": "...", "params": {}} assert q.to_dict() == d + + +@pytest.mark.parametrize( # type: ignore[misc] + "minimum_should_match", + [1, -1, "1", "-1", "50%", "-50%", "1<50%"], +) +def test_bool_with_minimum_should_match_as_int_or_string( + minimum_should_match: str, +) -> None: + + q1 = query.Bool( + minimum_should_match=minimum_should_match, + should=[ + query.Q("term", field="aa1"), + query.Q("term", field="aa2"), + ], + ) + + q2 = query.Bool( + minimum_should_match=minimum_should_match, + should=[ + query.Q("term", field="bb1"), + query.Q("term", field="bb2"), + ], + ) + + q3 = q1 & q2 + + d1 = { + "bool": { + "minimum_should_match": minimum_should_match, # input type is preserved + "should": [ + {"term": {"field": "aa1"}}, + {"term": {"field": "aa2"}}, + ], + } + } + + d2 = { + "bool": { + "minimum_should_match": minimum_should_match, # input type is preserved + "should": [ + {"term": {"field": "bb1"}}, + {"term": {"field": "bb2"}}, + ], + } + } + + d3 = { + "bool": {**d1["bool"], "must": [d2]}, + } + + assert q1.to_dict() == d1 + assert q2.to_dict() == d2 + assert q3.to_dict() == d3