Fixed filter bug.
This commit is contained in:
parent
2ad327fcd9
commit
2a73d0ac92
1 changed files with 36 additions and 10 deletions
|
|
@ -76,6 +76,20 @@ def _collect_tables_from_filters(filters) -> set:
|
||||||
visit(f)
|
visit(f)
|
||||||
return seen
|
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):
|
def _unwrap_ob(ob):
|
||||||
elem = getattr(ob, "element", None)
|
elem = getattr(ob, "element", None)
|
||||||
col = elem if elem is not None else ob
|
col = elem if elem is not None else ob
|
||||||
|
|
@ -244,6 +258,7 @@ class CRUDService(Generic[T]):
|
||||||
collection_field_names: Any
|
collection_field_names: Any
|
||||||
join_paths: Any
|
join_paths: Any
|
||||||
filter_tables: Any
|
filter_tables: Any
|
||||||
|
filter_table_keys: Any
|
||||||
req_fields: Any
|
req_fields: Any
|
||||||
proj_opts: Any
|
proj_opts: Any
|
||||||
|
|
||||||
|
|
@ -259,12 +274,19 @@ class CRUDService(Generic[T]):
|
||||||
join_paths = tuple(spec.get_join_paths())
|
join_paths = tuple(spec.get_join_paths())
|
||||||
filter_tables = _collect_tables_from_filters(filters)
|
filter_tables = _collect_tables_from_filters(filters)
|
||||||
_, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
|
_, 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(
|
return self._Plan(
|
||||||
spec=spec, filters=filters, order_by=order_by, limit=limit, offset=offset,
|
spec=spec, filters=filters, order_by=order_by, limit=limit, offset=offset,
|
||||||
root_fields=root_fields, rel_field_names=rel_field_names,
|
root_fields=root_fields, rel_field_names=rel_field_names,
|
||||||
root_field_names=root_field_names, collection_field_names=collection_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
|
req_fields=req_fields, proj_opts=proj_opts
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -278,16 +300,20 @@ class CRUDService(Generic[T]):
|
||||||
if base_alias is not root_alias:
|
if base_alias is not root_alias:
|
||||||
continue
|
continue
|
||||||
prop = getattr(rel_attr, "property", None)
|
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))
|
is_collection = bool(getattr(prop, "uselist", False))
|
||||||
|
|
||||||
sel = getattr(target_alias, "selectable", None)
|
if not is_collection:
|
||||||
sel_elem = getattr(sel, "element", None)
|
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
opt = selectinload(rel_attr)
|
opt = selectinload(rel_attr)
|
||||||
if is_collection:
|
if is_collection:
|
||||||
|
|
@ -407,7 +433,7 @@ class CRUDService(Generic[T]):
|
||||||
base = self._apply_not_deleted(base, root_alias, params)
|
base = self._apply_not_deleted(base, root_alias, params)
|
||||||
for _b, rel_attr, target_alias in plan.join_paths:
|
for _b, rel_attr, target_alias in plan.join_paths:
|
||||||
if not bool(getattr(getattr(rel_attr, "property", None), "uselist", False)):
|
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:
|
if plan.filters:
|
||||||
base = base.filter(*plan.filters)
|
base = base.filter(*plan.filters)
|
||||||
total = session.query(func.count()).select_from(
|
total = session.query(func.count()).select_from(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue