Skip to content

Commit fa8419e

Browse files
ed-cscostar1tsuu
andauthored
fix(db-mongodb): remove duplicate IDs in nested relationship queries (#16354)
### What? When querying through a nested relationship (e.g. `where: { 'movie.name': { equals: '...' } }`), the `$in` array used to filter parent collection documents was populated with duplicate entries — each document ID was pushed twice: once as a string and once as a `Types.ObjectId`. ### Why? In `buildSearchParams.ts`, the `else` branch (for non-`join` fields) was doing: **Before:** ```ts const stringID = doc._id.toString() $in.push(stringID) if (Types.ObjectId.isValid(stringID)) { $in.push(doc._id) } ``` Mongoose auto-casts 24-hex strings to ObjectId before sending queries to MongoDB, so both entries resolve to the same ObjectId. This caused every ID to appear twice in the $in array (e.g. 100 IDs → 200 entries), resulting in significantly larger queries, slower plaremnning times, and in extreme cases client disconnects due to timeouts. How? After: ```ts $in.push(doc._id) ``` The _id returned by Mongoose's .lean() is already the correct BSON type — ObjectId for standard collections, string/number for custom ID collections — so no extra casting is needed. A regression test was added to test/relationships/int.spec.ts that queries directors through a nested movie relationship using or conditions and asserts no duplicate documents are returned. --------- Co-authored-by: Sasha Rakhmatulin <sasha@ritsuko.dev>
1 parent b3b8198 commit fa8419e

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

packages/db-mongodb/src/queries/buildSearchParams.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,10 @@ export async function buildSearchParam({
217217
}
218218
} else {
219219
const stringID = doc._id.toString()
220-
$in.push(stringID)
221-
222220
if (Types.ObjectId.isValid(stringID)) {
223221
$in.push(doc._id)
222+
} else {
223+
$in.push(stringID)
224224
}
225225
}
226226
})

test/relationships/int.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,37 @@ describe('Relationships', () => {
473473
expect(res.docs[0].id).toBe(director_2.id)
474474
})
475475

476+
// MongoDB dedupes $in at execution, so the bug is only visible in the
477+
// filter Payload hands to Mongoose — not in the returned docs.
478+
mongoIt('should not duplicate IDs in $in when querying through a relationship', async () => {
479+
const movie = await payload.create({
480+
collection: 'movies',
481+
data: { name: 'dup_test_movie' },
482+
})
483+
484+
const Model = (payload.db as any).collections.directors
485+
const originalPaginate = Model.paginate.bind(Model)
486+
let capturedQuery: any
487+
Model.paginate = (query: any, ...rest: any[]) => {
488+
capturedQuery = query
489+
return originalPaginate(query, ...rest)
490+
}
491+
492+
try {
493+
await payload.find({
494+
collection: 'directors',
495+
where: { 'movie.name': { equals: 'dup_test_movie' } },
496+
})
497+
} finally {
498+
Model.paginate = originalPaginate
499+
}
500+
501+
// eslint-disable-next-line vitest/no-standalone-expect
502+
expect(capturedQuery.$and[0].movie.$in).toHaveLength(1)
503+
504+
await payload.delete({ collection: 'movies', id: movie.id })
505+
})
506+
476507
describe('hasMany relationships', () => {
477508
it('should retrieve totalDocs correctly with hasMany,', async () => {
478509
const movie1 = await payload.create({

0 commit comments

Comments
 (0)