Skip to content

Commit 9993d01

Browse files
committed
fix connection count bug
1 parent af67ea2 commit 9993d01

File tree

10 files changed

+155
-2
lines changed

10 files changed

+155
-2
lines changed

.changeset/four-islands-juggle.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pothos/plugin-drizzle": patch
3+
"@pothos/plugin-prisma": patch
4+
---
5+
6+
Fix a bug where selecting only connection count at the same time as another field selected the same relation would cause an error"

packages/plugin-drizzle/src/drizzle-field-builder.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ import {
2929
type Table,
3030
type TableRelationalConfig,
3131
} from 'drizzle-orm';
32-
import type { FieldNode } from 'graphql';
32+
import {
33+
type FieldNode,
34+
Kind as GraphQLKind,
35+
type GraphQLResolveInfo,
36+
getNamedType,
37+
isInterfaceType,
38+
isObjectType,
39+
} from 'graphql';
3340
import type { DrizzleRef } from './interface-ref';
3441
import type {
3542
DrizzleConnectionShape,
@@ -302,6 +309,7 @@ export class DrizzleObjectFieldBuilder<
302309
parent: unknown,
303310
args: PothosSchemaTypes.DefaultConnectionArguments,
304311
context: {},
312+
info: GraphQLResolveInfo,
305313
) => {
306314
const countKey = `_${name as string}_count`;
307315
const parentRecord = parent as Record<string, unknown>;
@@ -324,6 +332,34 @@ export class DrizzleObjectFieldBuilder<
324332
};
325333
}
326334

335+
// Detect totalCountOnly to skip cursor computation when only totalCount is requested
336+
const returnType = getNamedType(info.returnType);
337+
const fields =
338+
isObjectType(returnType) || isInterfaceType(returnType) ? returnType.getFields() : {};
339+
const totalCountOnly = info.fieldNodes.every((selection) =>
340+
selection.selectionSet?.selections.every(
341+
(s) =>
342+
s.kind === GraphQLKind.FIELD &&
343+
(fields[s.name.value]?.extensions?.pothosDrizzleTotalCount ||
344+
s.name.value === '__typename'),
345+
),
346+
);
347+
348+
if (totalCountOnly) {
349+
return {
350+
parent,
351+
args,
352+
totalCount: countValue,
353+
edges: [],
354+
pageInfo: {
355+
startCursor: null,
356+
endCursor: null,
357+
hasPreviousPage: false,
358+
hasNextPage: false,
359+
},
360+
};
361+
}
362+
327363
const { select, cursorColumns } = getQuery(args, context);
328364

329365
return wrapConnectionResult(
@@ -347,6 +383,9 @@ export class DrizzleObjectFieldBuilder<
347383
) => ({
348384
totalCount: t.int({
349385
nullable: false,
386+
extensions: {
387+
pothosDrizzleTotalCount: true,
388+
},
350389
resolve: (connection) => connection.totalCount,
351390
}),
352391
...(connectionOptions as { fields?: (t: unknown) => {} }).fields?.(t),

packages/plugin-drizzle/tests/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ type User implements Node {
206206
id: ID!
207207
isAdmin: Boolean!
208208
lastName: String
209+
postTitles: [String!]
209210
posts(limit: Int, offset: Int): [Post!]
210211
postsConnection(after: String, before: String, category: String, first: Int, invert: Boolean, last: Int, sortByCategory: Boolean): UserPostsConnection
211212
postsConnectionWithCount(after: String, before: String, first: Int, last: Int): UserPostsConnectionWithCountConnection

packages/plugin-drizzle/tests/example/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ type User implements Node {
164164
id: ID!
165165
isAdmin: Boolean!
166166
lastName: String
167+
postTitles: [String!]
167168
posts(limit: Int, offset: Int): [Post!]
168169
postsConnection(after: String, before: String, category: String, first: Int, invert: Boolean, last: Int, sortByCategory: Boolean): UserPostsConnection
169170
postsConnectionWithCount(after: String, before: String, first: Int, last: Int): UserPostsConnectionWithCountConnection

packages/plugin-drizzle/tests/example/schema/user.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,19 @@ export const User = builder.drizzleNode('users', {
292292
orderBy: { postId: 'desc' },
293293
}),
294294
}),
295+
postTitles: t.stringList({
296+
select: {
297+
with: {
298+
posts: {
299+
columns: {
300+
title: true,
301+
},
302+
limit: 3,
303+
},
304+
},
305+
},
306+
resolve: (user) => user.posts.map((p) => p.title),
307+
}),
295308
viewer: t.variant(Viewer, {
296309
select: {
297310
columns: {

packages/plugin-drizzle/tests/related-connection.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,4 +1280,39 @@ describe('related connections', () => {
12801280
}
12811281
`);
12821282
});
1283+
1284+
it('totalCount only with field selecting same relation without cursor (issue #1580)', async () => {
1285+
const context = await createContext({ userId: '1' });
1286+
clearDrizzleLogs();
1287+
const result = await execute({
1288+
schema,
1289+
document: gql`
1290+
query {
1291+
user(id: "VXNlcjox") {
1292+
postTitles
1293+
postsConnectionWithCount(first: 2) {
1294+
totalCount
1295+
}
1296+
}
1297+
}
1298+
`,
1299+
contextValue: context,
1300+
});
1301+
1302+
expect(result.errors).toBeUndefined();
1303+
expect(result.data).toMatchInlineSnapshot(`
1304+
{
1305+
"user": {
1306+
"postTitles": [
1307+
"Thalassinus ustilo hic civitas.",
1308+
"Altus suspendo textor ars teneo.",
1309+
"Terga depulso curia tenus.",
1310+
],
1311+
"postsConnectionWithCount": {
1312+
"totalCount": 15,
1313+
},
1314+
},
1315+
}
1316+
`);
1317+
});
12831318
});

packages/plugin-prisma/src/prisma-field-builder.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,25 @@ export class PrismaObjectFieldBuilder<
356356
parent: unknown,
357357
args: PothosSchemaTypes.DefaultConnectionArguments,
358358
context: {},
359+
info: GraphQLResolveInfo,
359360
) => {
361+
const returnType = getNamedType(info.returnType);
362+
const fields =
363+
isObjectType(returnType) || isInterfaceType(returnType) ? returnType.getFields() : {};
364+
const totalCountOnly = info.fieldNodes.every((selection) =>
365+
selection.selectionSet?.selections.every(
366+
(s) =>
367+
s.kind === GraphQLKind.FIELD &&
368+
(fields[s.name.value]?.extensions?.pothosPrismaTotalCount ||
369+
s.name.value === '__typename'),
370+
),
371+
);
372+
360373
const connectionQuery = getQuery(args, context);
361374

362375
return wrapConnectionResult(
363376
parent,
364-
(parent as Record<string, never>)[name] ?? [],
377+
totalCountOnly ? [] : ((parent as Record<string, never>)[name] ?? []),
365378
args,
366379
connectionQuery.take,
367380
formatCursor,

packages/plugin-prisma/tests/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ type SelectUser implements Node {
385385
id: ID!
386386
name: String
387387
postCount: Int!
388+
postTitles: [String!]
388389
posts: [SelectPost!]
389390
postsConnection(after: String, before: String, first: Int, last: Int, oldestFirst: Boolean): SelectUserPostsConnection
390391
profile: Profile!

packages/plugin-prisma/tests/counts.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,4 +913,39 @@ describe('prisma counts', () => {
913913
]
914914
`);
915915
});
916+
917+
it('totalCount only with field selecting same relation without cursor (issue #1580)', async () => {
918+
const query = gql`
919+
query {
920+
selectMe {
921+
postTitles
922+
postsConnection {
923+
totalCount
924+
}
925+
}
926+
}
927+
`;
928+
929+
const result = await execute({
930+
schema,
931+
document: query,
932+
contextValue: { user: { id: 1 } },
933+
});
934+
935+
expect(result.errors).toBeUndefined();
936+
expect(result.data).toMatchInlineSnapshot(`
937+
{
938+
"selectMe": {
939+
"postTitles": [
940+
"Quos distinctio distinctio dignissimos vel quo maiores ea.",
941+
"Voluptatem eum dolores dignissimos quia vel.",
942+
"Ut corrupti eum nostrum consequatur aliquam nostrum.",
943+
],
944+
"postsConnection": {
945+
"totalCount": 250,
946+
},
947+
},
948+
}
949+
`);
950+
});
916951
});

packages/plugin-prisma/tests/example/schema/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,15 @@ const SelectUser = builder.prismaNode('User', {
558558
onNull: 'error',
559559
}),
560560
postCount: t.relationCount('posts'),
561+
postTitles: t.stringList({
562+
select: {
563+
posts: {
564+
select: { title: true },
565+
take: 3,
566+
},
567+
},
568+
resolve: (user) => user.posts.map((p) => p.title),
569+
}),
561570
following: t.relatedConnection('following', {
562571
complexity: (args) => args.first ?? 1,
563572
cursor: 'compositeID',

0 commit comments

Comments
 (0)