Fixed filter bug.

This commit is contained in:
Yaro Kasear 2025-10-06 15:07:04 -05:00
parent 2ad327fcd9
commit 2a73d0ac92

View file

@ -76,6 +76,20 @@ def _collect_tables_from_filters(filters) -> set:
visit(f)
return seen
def _selectable_keys(sel) -> set[str]:
"""
Return a set of stable string keys for a selectable/alias and its base,
so we can match when when different alias objects are used.
"""
keys: set[str] = set()
cur = sel
while cur is not None:
k = getattr(cur, "key", None) or getattr(cur, "name", None)
if isinstance(k, str) and k:
keys.add(k)
cur = getattr(cur, "element", None)
return keys
def _unwrap_ob(ob):
elem = getattr(ob, "element", None)
col = elem if elem is not None else ob
@ -244,6 +258,7 @@ class CRUDService(Generic[T]):
collection_field_names: Any
join_paths: Any
filter_tables: Any
filter_table_keys: Any
req_fields: Any
proj_opts: Any
@ -259,12 +274,19 @@ class CRUDService(Generic[T]):
join_paths = tuple(spec.get_join_paths())
filter_tables = _collect_tables_from_filters(filters)
_, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
# Precompute a string-key set for quick/stable membership tests
fkeys: set[str] = set()
for t in filter_tables:
try:
fkeys |= _selectable_keys(t)
except Exception:
pass
return self._Plan(
spec=spec, filters=filters, order_by=order_by, limit=limit, offset=offset,
root_fields=root_fields, rel_field_names=rel_field_names,
root_field_names=root_field_names, collection_field_names=collection_field_names,
join_paths=join_paths, filter_tables=filter_tables,
join_paths=join_paths, filter_tables=filter_tables, filter_table_keys=fkeys,
req_fields=req_fields, proj_opts=proj_opts
)
@ -278,16 +300,20 @@ class CRUDService(Generic[T]):
if base_alias is not root_alias:
continue
prop = getattr(rel_attr, "property", None)
if log.isEnabledFor(logging.DEBUG):
try:
sel = getattr(target_alias, "selectable", None)
sel_key = getattr(sel, "key", None) or getattr(sel, "name", None)
log.debug(
"FIRST-HOP plan: rel=%s collection=%s selectable=%s filter_keys=%s",
rel_attr.key, getattr(prop, "uselist", False), sel_key, getattr(plan, "filter_table_keys", set())
)
except Exception:
pass
is_collection = bool(getattr(prop, "uselist", False))
sel = getattr(target_alias, "selectable", None)
sel_elem = getattr(sel, "element", None)
base_sel = sel_elem if sel_elem is not None else sel
needed_for_filter = (sel in plan.filter_tables) or (base_sel in plan.filter_tables)
if needed_for_filter and not is_collection:
query = query.join(rel_attr, isouter=True)
if not is_collection:
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
else:
opt = selectinload(rel_attr)
if is_collection:
@ -407,7 +433,7 @@ class CRUDService(Generic[T]):
base = self._apply_not_deleted(base, root_alias, params)
for _b, rel_attr, target_alias in plan.join_paths:
if not bool(getattr(getattr(rel_attr, "property", None), "uselist", False)):
base = base.join(rel_attr, isouter=True)
base = base.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
if plan.filters:
base = base.filter(*plan.filters)
total = session.query(func.count()).select_from(