Skip to content

Commit 89cb538

Browse files
Yuri ZmytrakovYuri Zmytrakov
authored andcommitted
fix parsing issues:
1 parent bd8e6ac commit 89cb538

File tree

3 files changed

+130
-44
lines changed

3 files changed

+130
-44
lines changed

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -877,13 +877,6 @@ async def post_search(
877877
query_fields = get_properties_from_cql2_filter(cql2_filter)
878878
await self.queryables_cache.validate(query_fields)
879879
search = await self.database.apply_cql2_filter(search, cql2_filter)
880-
date_str = getattr(search, "_cql2_date_str", None)
881-
collection_ids = getattr(search, "_cql2_collection_ids", None)
882-
if date_str is not None:
883-
datetime_parsed = format_datetime_range(date_str=date_str)
884-
search, datetime_search = self.database.apply_datetime_filter(
885-
search=search, datetime=datetime_parsed
886-
)
887880
except HTTPException:
888881
raise
889882
except Exception as e:

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import stac_fastapi.sfeos_helpers.filter as filter_module
1919
from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
20+
from stac_fastapi.core.datetime_utils import format_datetime_range
2021
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
2122
from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon, get_bool_env
2223
from stac_fastapi.extensions.core.transaction.request import (
@@ -57,7 +58,9 @@
5758
DatetimeOptimizer,
5859
to_es_via_ast,
5960
)
60-
from stac_fastapi.sfeos_helpers.filter.datetime_optimizer import extract_from_ast
61+
from stac_fastapi.sfeos_helpers.filter.datetime_optimizer import (
62+
extract_collection_datetime,
63+
)
6164
from stac_fastapi.sfeos_helpers.mappings import (
6265
AGGREGATION_MAPPING,
6366
COLLECTIONS_INDEX,
@@ -799,14 +802,12 @@ async def apply_cql2_filter(
799802
optimizer = DatetimeOptimizer()
800803
optimized_ast = optimizer.optimize_query_structure(ast)
801804

802-
date_str = extract_from_ast(optimized_ast, "datetime")
803-
collection_ids = extract_from_ast(optimized_ast, "collection") or None
805+
_cql2_collection_datetime = extract_collection_datetime(optimized_ast)
804806

805807
es_query = to_es_via_ast(queryables_mapping, optimized_ast)
806808

807809
search = search.filter(es_query)
808-
search._cql2_date_str = date_str
809-
search._cql2_collection_ids = collection_ids
810+
search._cql2_collection_datetime = _cql2_collection_datetime
810811

811812
except Exception:
812813
# Fallback to dictionary-based approach
@@ -866,9 +867,37 @@ async def execute_search(
866867
search_body: Dict[str, Any] = {}
867868
query = search.query.to_dict() if search.query else None
868869

869-
index_param = await self.async_index_selector.select_indexes(
870-
collection_ids, datetime_search
871-
)
870+
cql2_collection_datetime = getattr(search, "_cql2_collection_datetime", None)
871+
872+
# Special case for cql2 index selection
873+
if cql2_collection_datetime:
874+
index_param = ""
875+
for node in cql2_collection_datetime:
876+
if node[0]:
877+
collection_id = node[0]
878+
# Parse datetime for index select without changing Search object
879+
_, datetime_search = self.apply_datetime_filter(
880+
search, format_datetime_range(node[1])
881+
)
882+
if not isinstance(collection_id, list):
883+
collection_id = [collection_id]
884+
index_param_temp = await self.async_index_selector.select_indexes(
885+
collection_id, datetime_search
886+
)
887+
index_param = ",".join(
888+
p for p in (index_param, index_param_temp) if p
889+
)
890+
891+
collection_ids = []
892+
for collections, _ in cql2_collection_datetime:
893+
if isinstance(collections, list):
894+
collection_ids.extend(collections)
895+
else:
896+
collection_ids.append(collections)
897+
else:
898+
index_param = await self.async_index_selector.select_indexes(
899+
collection_ids, datetime_search
900+
)
872901
if len(index_param) > ES_MAX_URL_LENGTH - 300:
873902
index_param = ITEM_INDICES
874903
query = add_collections_to_body(collection_ids, query)

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/filter/datetime_optimizer.py

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -127,44 +127,108 @@ def _contains_datetime(self, node: CqlNode) -> bool:
127127
def extract_from_ast(node: CqlNode, field_name: str):
128128
"""Extract values from AST for a given field."""
129129
values = []
130-
datetime_start = None
131-
datetime_end = None
132130

133131
def recurse(n):
134-
nonlocal datetime_start, datetime_end
135-
132+
"""Run field extraction in recursion."""
136133
if hasattr(n, "children"):
137134
for child in n.children:
138135
recurse(child)
139136

140137
if hasattr(n, "field") and hasattr(n, "value"):
141138
if n.field == field_name:
142-
if field_name == "datetime":
143-
op = getattr(n, "op", None)
144-
if op == ComparisonOp.GTE:
145-
datetime_start = n.value
146-
elif op == ComparisonOp.LTE:
147-
datetime_end = n.value
148-
else:
149-
values.append(n.value)
139+
if isinstance(n.value, list):
140+
values.extend(n.value)
150141
else:
151-
if isinstance(n.value, list):
152-
values.extend(n.value)
153-
else:
154-
values.append(n.value)
142+
values.append(n.value)
143+
# Handle datetime range optimization
144+
elif hasattr(n, "op") and n.op == LogicalOp.AND and hasattr(n, "children"):
145+
# Check if this is a datetime range (GTE and LTE on datetime field)
146+
datetime_nodes = []
147+
for child in n.children:
148+
if (
149+
hasattr(child, "field")
150+
and child.field == "datetime"
151+
and hasattr(child, "op")
152+
and hasattr(child, "value")
153+
):
154+
datetime_nodes.append(child)
155+
156+
if len(datetime_nodes) == 2:
157+
# Check if we have both GTE and LTE for datetime
158+
gte_node = None
159+
lte_node = None
160+
for d_node in datetime_nodes:
161+
if d_node.op == ComparisonOp.GTE:
162+
gte_node = d_node
163+
elif d_node.op == ComparisonOp.LTE:
164+
lte_node = d_node
165+
166+
if gte_node and lte_node:
167+
values.append(
168+
{
169+
"type": "range",
170+
"start": gte_node.value,
171+
"end": lte_node.value,
172+
}
173+
)
155174

156175
recurse(node)
157176

158-
if field_name == "datetime":
159-
if datetime_start and datetime_end:
160-
return f"{datetime_start}/{datetime_end}"
161-
elif datetime_start:
162-
return f"{datetime_start}/.."
163-
elif datetime_end:
164-
return f"../{datetime_end}"
165-
elif values:
166-
return values[0]
167-
else:
168-
return None
169-
170-
return values
177+
return values if values else None
178+
179+
180+
def extract_collection_datetime(node):
181+
"""Get (collection, datetime_range) pairs from node."""
182+
pairs = []
183+
184+
def recurse(n):
185+
# Check if this is an AND node (we're looking for AND clauses)
186+
if hasattr(n, "op") and hasattr(n.op, "value") and n.op.value == "and":
187+
collection = None
188+
gte_date = None
189+
lte_date = None
190+
191+
# Look through all children of this AND node
192+
if hasattr(n, "children"):
193+
for child in n.children:
194+
# Check if it's a comparison node
195+
if (
196+
hasattr(child, "op")
197+
and hasattr(child, "field")
198+
and hasattr(child, "value")
199+
):
200+
if child.field == "collection":
201+
collection = child.value
202+
elif child.field in [
203+
"datetime",
204+
"start_datetime",
205+
"end_datetime",
206+
]: # Handle all datetime fields
207+
if hasattr(child.op, "value"):
208+
if child.op.value == ">=":
209+
gte_date = child.value
210+
elif child.op.value == "<=":
211+
lte_date = child.value
212+
213+
# If we found dates, add to pairs (even without collection)
214+
if gte_date or lte_date:
215+
if gte_date and lte_date:
216+
date_range = f"{gte_date}/{lte_date}"
217+
elif gte_date:
218+
date_range = f"{gte_date}/.."
219+
elif lte_date:
220+
date_range = f"../{lte_date}"
221+
222+
# Add to pairs - if no collection, use empty string
223+
pairs.append((collection or "", date_range))
224+
# If we found a collection but no dates
225+
elif collection is not None:
226+
pairs.append((collection, ""))
227+
228+
# Continue searching through children
229+
if hasattr(n, "children"):
230+
for child in n.children:
231+
recurse(child)
232+
233+
recurse(node)
234+
return pairs

0 commit comments

Comments
 (0)