From 162b22e65311c9844e9b3b4c02aa8201618425dd Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:14:10 +0800 Subject: [PATCH 1/2] fix: issue with in memory distinct when distinct fields are not selected --- .../src/client/crud/operations/base.ts | 22 ++++++++++++++++--- packages/runtime/test/client-api/find.test.ts | 20 ++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/client/crud/operations/base.ts b/packages/runtime/src/client/crud/operations/base.ts index 58fe3759..66d3b76b 100644 --- a/packages/runtime/src/client/crud/operations/base.ts +++ b/packages/runtime/src/client/crud/operations/base.ts @@ -177,12 +177,21 @@ export abstract class BaseOperationHandler { // distinct let inMemoryDistinct: string[] | undefined = undefined; if (args?.distinct) { - const distinct = ensureArray(args.distinct); + const distinct = ensureArray(args.distinct) as string[]; if (this.dialect.supportsDistinctOn) { - query = query.distinctOn(distinct.map((f: any) => sql.ref(`${model}.${f}`))); + query = query.distinctOn(distinct.map((f) => sql.ref(`${model}.${f}`))); } else { // in-memory distinct after fetching all results inMemoryDistinct = distinct; + + // make sure distinct fields are selected + query = distinct.reduce( + (acc, field) => + acc.select((eb) => + buildFieldRef(this.schema, model, field, this.options, eb).as(`$distinct$${field}`), + ), + query, + ); } } @@ -225,13 +234,20 @@ export abstract class BaseOperationHandler { const distinctResult: Record[] = []; const seen = new Set(); for (const r of result as any[]) { - const key = safeJSONStringify(inMemoryDistinct.map((f) => r[f]))!; + const key = safeJSONStringify(inMemoryDistinct.map((f) => r[`$distinct$${f}`]))!; if (!seen.has(key)) { distinctResult.push(r); seen.add(key); } } result = distinctResult; + + // clean up distinct utility fields + for (const r of result) { + for (const f of inMemoryDistinct) { + delete r[`$distinct$${f}`]; + } + } } return result; diff --git a/packages/runtime/test/client-api/find.test.ts b/packages/runtime/test/client-api/find.test.ts index 3cb85495..e952b599 100644 --- a/packages/runtime/test/client-api/find.test.ts +++ b/packages/runtime/test/client-api/find.test.ts @@ -244,6 +244,7 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client find tests for $provider', await createUser(client, 'u1@test.com', { name: 'Admin1', role: 'ADMIN', + profile: { create: { bio: 'Bio1' } }, }); await createUser(client, 'u3@test.com', { name: 'User', @@ -252,6 +253,7 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client find tests for $provider', await createUser(client, 'u2@test.com', { name: 'Admin2', role: 'ADMIN', + profile: { create: { bio: 'Bio1' } }, }); await createUser(client, 'u4@test.com', { name: 'User', @@ -259,7 +261,7 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client find tests for $provider', }); // single field distinct - let r = await client.user.findMany({ distinct: ['role'] }); + let r: any = await client.user.findMany({ distinct: ['role'] }); expect(r).toHaveLength(2); expect(r).toEqual( expect.arrayContaining([ @@ -268,6 +270,22 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client find tests for $provider', ]), ); + // distinct with include + r = await client.user.findMany({ distinct: ['role'], include: { profile: true } }); + expect(r).toHaveLength(2); + expect(r).toEqual( + expect.arrayContaining([ + expect.objectContaining({ role: 'ADMIN', profile: expect.any(Object) }), + expect.objectContaining({ role: 'USER', profile: null }), + ]), + ); + + // distinct with select + r = await client.user.findMany({ distinct: ['role'], select: { email: true } }); + console.log(r); + expect(r).toHaveLength(2); + expect(r).toEqual(expect.arrayContaining([{ email: expect.any(String) }, { email: expect.any(String) }])); + // multiple fields distinct r = await client.user.findMany({ distinct: ['role', 'name'], From dc0daef972e7a746bd3ff3a9047a4a6c6fd5a479 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:20:46 +0800 Subject: [PATCH 2/2] addressing PR comments --- packages/runtime/src/client/crud/operations/base.ts | 6 +++--- packages/runtime/test/client-api/find.test.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/client/crud/operations/base.ts b/packages/runtime/src/client/crud/operations/base.ts index 66d3b76b..cc79057f 100644 --- a/packages/runtime/src/client/crud/operations/base.ts +++ b/packages/runtime/src/client/crud/operations/base.ts @@ -244,9 +244,9 @@ export abstract class BaseOperationHandler { // clean up distinct utility fields for (const r of result) { - for (const f of inMemoryDistinct) { - delete r[`$distinct$${f}`]; - } + Object.keys(r) + .filter((k) => k.startsWith('$distinct$')) + .forEach((k) => delete r[k]); } } diff --git a/packages/runtime/test/client-api/find.test.ts b/packages/runtime/test/client-api/find.test.ts index e952b599..9b588a03 100644 --- a/packages/runtime/test/client-api/find.test.ts +++ b/packages/runtime/test/client-api/find.test.ts @@ -282,7 +282,6 @@ describe.each(createClientSpecs(PG_DB_NAME))('Client find tests for $provider', // distinct with select r = await client.user.findMany({ distinct: ['role'], select: { email: true } }); - console.log(r); expect(r).toHaveLength(2); expect(r).toEqual(expect.arrayContaining([{ email: expect.any(String) }, { email: expect.any(String) }]));