From 7db182041e1af0b92d3714802ceaf7cdef51daa0 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 26 Sep 2025 12:40:00 -0500 Subject: [PATCH] More patching. --- crudkit/core/service.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crudkit/core/service.py b/crudkit/core/service.py index ad6338f..e2e1ab4 100644 --- a/crudkit/core/service.py +++ b/crudkit/core/service.py @@ -539,7 +539,7 @@ class CRUDService(Generic[T]): limit, offset = spec.parse_pagination() # Includes / fields (populates join_paths) - root_fields, rel_field_names, root_field_names = spec.parse_fields() + root_fields, rel_field_names, root_field_names, collection_field_names = spec.parse_fields() spec.parse_includes() join_paths = tuple(spec.get_join_paths()) @@ -548,12 +548,24 @@ class CRUDService(Generic[T]): if only_cols: query = query.options(Load(root_alias).load_only(*only_cols)) - # JOIN all paths we resolved and hydrate them from the join + # JOIN non-collection paths; selectinload for collections used_contains_eager = False for _base_alias, rel_attr, target_alias in join_paths: - 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 + is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False)) + if is_collection: + opt = selectinload(rel_attr) + 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 # Filters AFTER joins → no cartesian products if filters: