WIP on getting work_log support on inventory page.
This commit is contained in:
parent
7935b5b57d
commit
57104824a1
5 changed files with 117 additions and 68 deletions
|
|
@ -247,32 +247,30 @@ class CRUDService(Generic[T]):
|
|||
# Detect first hops that have deeper, nested tails requested (e.g. "contact.supervisor")
|
||||
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
|
||||
|
||||
used_contains_eager = False
|
||||
# IMPORTANT:
|
||||
# - Only attach loader options for first-hop relations from the root.
|
||||
# - Always use selectinload here (avoid contains_eager joins).
|
||||
# - Let compile_projections() supply deep chained options.
|
||||
for base_alias, rel_attr, target_alias in join_paths:
|
||||
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
|
||||
is_firsthop_from_root = (base_alias is root_alias)
|
||||
if not is_firsthop_from_root:
|
||||
# Deeper hops are handled by proj_opts below
|
||||
continue
|
||||
prop = getattr(rel_attr, "property", None)
|
||||
is_collection = bool(getattr(prop, "uselist", False))
|
||||
is_nested_firsthop = rel_attr.key in nested_first_hops
|
||||
|
||||
if is_collection or is_nested_firsthop:
|
||||
# Use selectinload so deeper hops can chain cleanly (and to avoid
|
||||
# contains_eager/loader conflicts on nested paths).
|
||||
opt = selectinload(rel_attr)
|
||||
|
||||
# Narrow columns for collections if we know child scalar names
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = rel_attr.property.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
|
||||
query = query.options(opt)
|
||||
else:
|
||||
# Simple first-hop scalar rel with no deeper tails: safe to join + contains_eager
|
||||
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
|
||||
query = query.options(contains_eager(rel_attr, alias=target_alias))
|
||||
used_contains_eager = True
|
||||
opt = selectinload(rel_attr)
|
||||
# Optional narrowng for collections
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = prop.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
query = query.options(opt)
|
||||
|
||||
# Filters AFTER joins → no cartesian products
|
||||
if filters:
|
||||
|
|
@ -365,6 +363,10 @@ class CRUDService(Generic[T]):
|
|||
last_key = None
|
||||
|
||||
# Count DISTINCT ids with mirrored joins
|
||||
|
||||
# Apply deep projection loader options (safe: we avoided contains_eager)
|
||||
if proj_opts:
|
||||
query = query.options(*proj_opts)
|
||||
total = None
|
||||
if include_total:
|
||||
base = session.query(getattr(root_alias, "id"))
|
||||
|
|
@ -466,26 +468,25 @@ class CRUDService(Generic[T]):
|
|||
|
||||
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
|
||||
|
||||
used_contains_eager = False
|
||||
# First-hop only; use selectinload (no contains_eager)
|
||||
for base_alias, rel_attr, target_alias in join_paths:
|
||||
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
|
||||
is_nested_firsthop = rel_attr.key in nested_first_hops
|
||||
is_firsthop_from_root = (base_alias is root_alias)
|
||||
if not is_firsthop_from_root:
|
||||
continue
|
||||
prop = getattr(rel_attr, "property", None)
|
||||
is_collection = bool(getattr(prop, "uselist", False))
|
||||
_is_nested_firsthop = rel_attr.key in nested_first_hops
|
||||
|
||||
if is_collection or is_nested_firsthop:
|
||||
opt = selectinload(rel_attr)
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = rel_attr.property.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
query = query.options(opt)
|
||||
else:
|
||||
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
|
||||
query = query.options(contains_eager(rel_attr, alias=target_alias))
|
||||
used_contains_eager = True
|
||||
opt = selectinload(rel_attr)
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = prop.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
query = query.options(opt)
|
||||
|
||||
# Apply filters (joins are in place → no cartesian products)
|
||||
if filters:
|
||||
|
|
@ -497,7 +498,7 @@ class CRUDService(Generic[T]):
|
|||
# Projection loader options compiled from requested fields.
|
||||
# Skip if we used contains_eager to avoid loader-strategy conflicts.
|
||||
expanded_fields, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
|
||||
if proj_opts and not used_contains_eager:
|
||||
if proj_opts:
|
||||
query = query.options(*proj_opts)
|
||||
|
||||
obj = query.first()
|
||||
|
|
@ -565,26 +566,25 @@ class CRUDService(Generic[T]):
|
|||
|
||||
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
|
||||
|
||||
used_contains_eager = False
|
||||
for _base_alias, rel_attr, target_alias in join_paths:
|
||||
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
|
||||
is_nested_firsthop = rel_attr.key in nested_first_hops
|
||||
# First-hop only; use selectinload
|
||||
for base_alias, rel_attr, target_alias in join_paths:
|
||||
is_firsthop_from_root = (base_alias is root_alias)
|
||||
if not is_firsthop_from_root:
|
||||
continue
|
||||
prop = getattr(rel_attr, "property", None)
|
||||
is_collection = bool(getattr(prop, "uselist", False))
|
||||
_is_nested_firsthop = rel_attr.key in nested_first_hops
|
||||
|
||||
if is_collection or is_nested_firsthop:
|
||||
opt = selectinload(rel_attr)
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = rel_attr.property.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
query = query.options(opt)
|
||||
else:
|
||||
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
|
||||
query = query.options(contains_eager(rel_attr, alias=target_alias))
|
||||
used_contains_eager = True
|
||||
opt = selectinload(rel_attr)
|
||||
if is_collection:
|
||||
child_names = (collection_field_names or {}).get(rel_attr.key, [])
|
||||
if child_names:
|
||||
target_cls = prop.mapper.class_
|
||||
cols = [getattr(target_cls, n, None) for n in child_names]
|
||||
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
|
||||
if cols:
|
||||
opt = opt.load_only(*cols)
|
||||
query = query.options(opt)
|
||||
|
||||
# Filters AFTER joins → no cartesian products
|
||||
if filters:
|
||||
|
|
@ -608,7 +608,7 @@ class CRUDService(Generic[T]):
|
|||
|
||||
# Projection loaders only if we didn’t use contains_eager
|
||||
expanded_fields, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
|
||||
if proj_opts and not used_contains_eager:
|
||||
if proj_opts:
|
||||
query = query.options(*proj_opts)
|
||||
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue