From d15ee8540a592e721489fc704905a0e03436bab4 Mon Sep 17 00:00:00 2001 From: John Murner Date: Mon, 19 May 2025 17:22:42 -0500 Subject: [PATCH 1/2] Prevent property not allowed in terms query --- .../stac_fastapi/core/extensions/filter.py | 20 ++++++++++++++++--- .../core/stac_fastapi/core/version.py | 2 +- stac_fastapi/elasticsearch/setup.py | 2 +- stac_fastapi/opensearch/setup.py | 2 +- stac_fastapi/tests/extensions/test_filter.py | 18 +++++++++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/stac_fastapi/core/stac_fastapi/core/extensions/filter.py b/stac_fastapi/core/stac_fastapi/core/extensions/filter.py index 078e7fbf..c156d7ab 100644 --- a/stac_fastapi/core/stac_fastapi/core/extensions/filter.py +++ b/stac_fastapi/core/stac_fastapi/core/extensions/filter.py @@ -27,6 +27,14 @@ } +def _get_value_for_terms_query(value: dict): + """Return property value for terms queries.""" + if isinstance(value, dict): + if "property" in value: + return value["property"] + return value + + def _replace_like_patterns(match: re.Match) -> str: pattern = match.group() try: @@ -159,9 +167,15 @@ def to_es(queryables_mapping: Dict[str, Any], query: Dict[str, Any]) -> Dict[str return {"range": {field: {range_op[query["op"]]: value}}} else: if query["op"] == ComparisonOp.EQ: - return {"term": {field: value}} + return {"term": {field: _get_value_for_terms_query(value)}} elif query["op"] == ComparisonOp.NEQ: - return {"bool": {"must_not": [{"term": {field: value}}]}} + return { + "bool": { + "must_not": [ + {"term": {field: _get_value_for_terms_query(value)}} + ] + } + } else: return {"range": {field: {range_op[query["op"]]: value}}} @@ -183,7 +197,7 @@ def to_es(queryables_mapping: Dict[str, Any], query: Dict[str, Any]) -> Dict[str values = query["args"][1] if not isinstance(values, list): raise ValueError(f"Arg {values} is not a list") - return {"terms": {field: values}} + return {"terms": {field: [_get_value_for_terms_query(x) for x in values]}} elif query["op"] == AdvancedComparisonOp.LIKE: field = to_es_field(queryables_mapping, query["args"][0]["property"]) diff --git a/stac_fastapi/core/stac_fastapi/core/version.py b/stac_fastapi/core/stac_fastapi/core/version.py index 1cd0ed04..659f8b06 100644 --- a/stac_fastapi/core/stac_fastapi/core/version.py +++ b/stac_fastapi/core/stac_fastapi/core/version.py @@ -1,2 +1,2 @@ """library version.""" -__version__ = "4.2.0" +__version__ = "4.2.1" diff --git a/stac_fastapi/elasticsearch/setup.py b/stac_fastapi/elasticsearch/setup.py index 06b8e880..e79a245d 100644 --- a/stac_fastapi/elasticsearch/setup.py +++ b/stac_fastapi/elasticsearch/setup.py @@ -6,7 +6,7 @@ desc = f.read() install_requires = [ - "stac-fastapi-core==4.2.0", + "stac-fastapi-core==4.2.1", "elasticsearch[async]~=8.18.0", "uvicorn~=0.23.0", "starlette>=0.35.0,<0.36.0", diff --git a/stac_fastapi/opensearch/setup.py b/stac_fastapi/opensearch/setup.py index 7fe18f87..b1f43810 100644 --- a/stac_fastapi/opensearch/setup.py +++ b/stac_fastapi/opensearch/setup.py @@ -6,7 +6,7 @@ desc = f.read() install_requires = [ - "stac-fastapi-core==4.2.0", + "stac-fastapi-core==4.2.1", "opensearch-py~=2.8.0", "opensearch-py[async]~=2.8.0", "uvicorn~=0.23.0", diff --git a/stac_fastapi/tests/extensions/test_filter.py b/stac_fastapi/tests/extensions/test_filter.py index fb6bc850..a6cb25e4 100644 --- a/stac_fastapi/tests/extensions/test_filter.py +++ b/stac_fastapi/tests/extensions/test_filter.py @@ -159,6 +159,24 @@ async def test_search_filter_ext_and_get_cql2text_id(app_client, ctx): assert len(resp.json()["features"]) == 1 +@pytest.mark.asyncio +async def test_search_filter_ext_cql_property_terms_query(app_client, ctx): + """Tests for terms queries not supporting property reference.""" + landsat_row = ctx.item["properties"]["landsat:row"] + + cql2_text_property_queries = { + f'properties.landsat:row="{landsat_row}"': 1, + f'properties.landsat:row!="{landsat_row}"': 0, + f'properties.landsat:row IN ("{landsat_row}")': 1, + } + + for filter, result in cql2_text_property_queries.items(): + resp = await app_client.get(f"/search?filter-lang=cql2-text&filter={filter}") + content = resp.json() + assert resp.status_code == 200 + assert len(content["features"]) == result + + @pytest.mark.asyncio async def test_search_filter_ext_and_get_cql2text_cloud_cover(app_client, ctx): collection = ctx.item["collection"] From 7e1db97d7d2aa665ec632d0df517bd47ec97aa58 Mon Sep 17 00:00:00 2001 From: John Murner Date: Wed, 21 May 2025 21:00:51 +0200 Subject: [PATCH 2/2] Change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc84550..ee1533b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +- Filter Extension - patch terms value to prevent 'property reference not allowed in terms query' 500 + ### Added ### Changed