Skip to content

fix(repository): dedupe/filter source ids before querying in hasMany and hasOne inclusion resolvers - #11764

Open
karthikchundi-commits wants to merge 1 commit into
loopbackio:masterfrom
karthikchundi-commits:fix/inclusion-resolver-source-id-mutation
Open

fix(repository): dedupe/filter source ids before querying in hasMany and hasOne inclusion resolvers#11764
karthikchundi-commits wants to merge 1 commit into
loopbackio:masterfrom
karthikchundi-commits:fix/inclusion-resolver-source-id-mutation

Conversation

@karthikchundi-commits

Copy link
Copy Markdown

Summary

createHasManyInclusionResolver and createHasOneInclusionResolver both build their list of source ids (sourceIds / sourceIdsCategorized[k]) and then pass that array by reference straight into findByForeignKeys(). That function wraps a multi-element array in an {inq: [...]} where clause and hands the same array reference on to the target repository's find() - it never clones it.

belongsTo and referencesMany's inclusion resolvers don't have this problem: both already call deduplicate(sourceIds).filter(e => e) (a fresh array) before querying. hasMany and hasOne didn't - this PR brings them in line.

The actual bug (not just theoretical)

I initially expected the risk here to be "an undefined id collapses the where clause and over-fetches unrelated data" via JSON-serialization dropping an undefined-valued key. That's not what I found when I actually reproduced it.

What's really happening: because the raw, unfiltered array is passed by reference, if anything downstream (a connector, a query-normalization layer) sanitizes the inq array in place - e.g. stripping falsy values before running the query, which is exactly what the built-in memory connector does - that mutation happens to the same array object the resolver still needs, unmodified, a few lines later to zip the fetched results back onto each original source entity (flattenTargetsOfOneToManyRelation/flattenTargetsOfOneToOneRelation expect sourceIds.length to still equal the original entity count).

I confirmed this concretely with a standalone script (real compiled resolvers + a real in-memory DataSource, @hasMany/@hasOne models, no mocks):

  • 3 entities in the batch, the third with an undefined id (e.g. as if its key field had been excluded by a fields filter).
  • Before this fix: the resolver returns an array of length 2, not 3 - the third element isn't merely undefined, it's gone, silently misaligning every subsequent lookup against the wrong source entity.
  • After this fix: the resolver returns the correct length-3 array, with undefined in the third slot as expected.

So this isn't just a wasted-query-size nitpick - it's a real correctness/data-integrity bug: any hasMany/hasOne inclusion over a batch containing a source entity with a missing key (or a duplicate id, which triggers similar inq-array handling) can return relation data misaligned to the wrong parent entities.

Fix

Pass deduplicate(sourceIds).filter(e => e) (a fresh array, matching the existing belongsTo/referencesMany convention exactly) into findByForeignKeys(), while leaving the original sourceIds / sourceIdsCategorized[k] array completely untouched for the later flatten/zip step. No new helper needed - deduplicate already exists in relation.helpers.ts and is already used by the two siblings.

Testing

Added a regression test to each of the two existing acceptance suites (has-one.inclusion-resolver.acceptance.ts, has-many-inclusion-resolver.relation.acceptance.ts), calling the registered inclusionResolver directly with a batch containing a duplicate id and a missing id, asserting the correct length and per-entity alignment. These run against every connector already covered by the acceptance-suite loader (acceptance/repository-{mongodb,mysql,postgresql,cloudant}) once merged.

I don't have Docker/a live external database in my environment, so I couldn't run those acceptance packages themselves end-to-end. What I did verify for real, locally:

  • npm run build for both packages/repository and packages/repository-tests - clean.
  • eslint and prettier --check on all four changed files - clean.
  • A standalone script (not included in this PR - just my own local verification) exercising the actual compiled resolvers against a real in-memory DataSource with real @hasMany/@hasOne-decorated models, confirming both the bug (by temporarily reverting just the source fix) and the fix.

Checklist

  • npm run build passes locally
  • npm run lint (eslint + prettier) passes locally on changed files
  • New tests added for the fix
  • Full acceptance suite (npm test) run locally - not possible here, no Docker/live DB; would appreciate a CI run

…and hasOne inclusion resolvers

Both resolvers passed their raw, unfiltered source-id array by
reference straight to findByForeignKeys(), which wraps it in an
{inq: [...]} where clause and hands that same array on to the
connector. A connector/query layer that sanitizes an inq array in
place (e.g. stripping falsy values before running the query, as the
in-memory connector does) then mutates that shared array out from
under the caller - shrinking the very array each resolver still
needed, unmodified, to zip results back onto each original entity.
That silently truncated and misaligned the resolver's return value
whenever any source entity's key was undefined (e.g. excluded by a
fields filter) or duplicated another entity's.

belongsTo and referencesMany already pass a fresh, deduplicated,
filtered array (never the original reference) before querying;
hasMany and hasOne now do the same.

Confirmed via a standalone script against the real compiled resolvers
and an in-memory DataSource: reverting just this fix reproduces the
truncated/misaligned result (length 2 instead of 3 for a 3-entity
batch with one undefined key); with the fix, the result is correctly
length 3 with undefined in the right position.

Assisted-by: AI
Signed-off-by: Karth <karthik.chundi@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant