You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf: batch lookup-type resolution to eliminate table-render N+1 [3.x] (#138)
* perf: batch lookup-type resolution to eliminate table-render N+1
LookupResolver previously fired one query per row when rendering
SingleChoice/MultiChoice columns with a lookup_type. For a 50-row
table that meant 50 queries to the lookup model.
Introduces a request-scoped LookupCache that sits behind the resolver
and is primed proactively by scopeWithCustomFieldValues via afterQuery.
The preloader scans loaded customFieldValues, groups referenced IDs by
lookup_type, and fetches all titles in one query per lookup_type.
Tables and infolists that already use withCustomFieldValues() get this
for free — per-row resolve() calls hit the cache and fire zero queries.
Tables that don't call the scope still benefit: repeated IDs across
rows or columns collapse to a single query.
Tests cover:
- LookupCache contract (remember/titleFor/missing/flush + dedup + scoping)
- Resolver: second call with same IDs fires zero queries
- Resolver: partial cache hit only queries missing IDs
- Resolver: non-scalar values skipped without hitting DB
- Resolver: titles returned in submission order
- Full preload: withCustomFieldValues + per-row resolve = 0 queries to lookup model
* fix: bind LookupCache as scoped so queue workers don't leak titles across jobs
* refactor: extract LookupAttributeResolver; null-check resource; clearer error
- Single source of truth for (lookupInstance, titleAttribute) resolution,
used by both LookupResolver and LookupPreloader.
- Throws RuntimeException early when Filament::getModelResource returns null
instead of silently calling app(null) and crashing in the container.
- Rewords MissingRecordTitleAttributeException to mention both the lookup
model and the resource so the error is actionable.
- Preloader handles Collection/Arrayable values (multi-choice lookups stored
in json_value via the AsCollection cast) that were previously skipped.
- Preloader conditionals split into single-concern continues to satisfy
ChangeOrIfContinueToMultiContinueRector.
- scalarIdsFromValue uses in_array(..., true) for the null/''/[] short-circuit
to satisfy RepeatedOrEqualToInArrayRector.
- LookupResolver filters blank string ids up front so we never fire a
whereIn containing an empty string and never cache entries under ''.
Review threads: singleton→scoped, Collection handling, null resource,
empty strings, duplicated helper, and error message clarity.
* test: add regression tests for Collection values and blank strings; use scoped query log
0 commit comments