Skip to content

Commit 66cc8e0

Browse files
ymc9claude
andcommitted
fix(orm): split jsonb_build_object calls exceeding PostgreSQL 100-arg limit
When including a nested relation with 51+ columns, the generated jsonb_build_object call would exceed PostgreSQL's FUNC_MAX_ARGS limit of 100 (error code 54023). Fix by chunking the key-value pairs into groups of 50 and concatenating the resulting jsonb objects with ||. Fixes #2524 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0495333 commit 66cc8e0

2 files changed

Lines changed: 112 additions & 4 deletions

File tree

packages/orm/src/client/crud/dialects/postgresql.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,10 +335,25 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends LateralJoinDi
335335
}
336336

337337
override buildJsonObject(value: Record<string, Expression<unknown>>) {
338-
return this.eb.fn(
339-
'jsonb_build_object',
340-
Object.entries(value).flatMap(([key, value]) => [sql.lit(key), value]),
341-
);
338+
const entries = Object.entries(value);
339+
340+
// PostgreSQL's FUNC_MAX_ARGS limit is 100. jsonb_build_object takes key-value pairs,
341+
// so at most 50 pairs (100 args) fit in one call. Split larger objects and merge with ||.
342+
const MAX_PAIRS = 50;
343+
344+
const buildChunk = (chunk: [string, Expression<unknown>][]) =>
345+
this.eb.fn('jsonb_build_object', chunk.flatMap(([k, v]) => [sql.lit(k), v]));
346+
347+
if (entries.length <= MAX_PAIRS) {
348+
return buildChunk(entries);
349+
}
350+
351+
const chunks: Expression<unknown>[] = [];
352+
for (let i = 0; i < entries.length; i += MAX_PAIRS) {
353+
chunks.push(buildChunk(entries.slice(i, i + MAX_PAIRS)));
354+
}
355+
356+
return chunks.reduce((acc, chunk) => sql`${acc} || ${chunk}`) as AliasableExpression<unknown>;
342357
}
343358

344359
override castInt<T extends Expression<any>>(expression: T): T {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
// https://github.com/zenstackhq/zenstack/issues/2524
5+
describe('Regression for issue #2524', () => {
6+
it('should not exceed PostgreSQL 100-argument limit when including a relation with 51+ columns', async () => {
7+
// jsonb_build_object takes key-value pairs, so 51 columns = 102 arguments,
8+
// exceeding PostgreSQL's FUNC_MAX_ARGS limit of 100 (error code 54023)
9+
const db = await createTestClient(
10+
`
11+
model Post {
12+
id String @id @default(cuid())
13+
opportunities Opportunity[]
14+
}
15+
16+
model Opportunity {
17+
id String @id @default(cuid())
18+
postId String
19+
post Post @relation(fields: [postId], references: [id])
20+
col01 String @default("")
21+
col02 String @default("")
22+
col03 String @default("")
23+
col04 String @default("")
24+
col05 String @default("")
25+
col06 String @default("")
26+
col07 String @default("")
27+
col08 String @default("")
28+
col09 String @default("")
29+
col10 String @default("")
30+
col11 String @default("")
31+
col12 String @default("")
32+
col13 String @default("")
33+
col14 String @default("")
34+
col15 String @default("")
35+
col16 String @default("")
36+
col17 String @default("")
37+
col18 String @default("")
38+
col19 String @default("")
39+
col20 String @default("")
40+
col21 String @default("")
41+
col22 String @default("")
42+
col23 String @default("")
43+
col24 String @default("")
44+
col25 String @default("")
45+
col26 String @default("")
46+
col27 String @default("")
47+
col28 String @default("")
48+
col29 String @default("")
49+
col30 String @default("")
50+
col31 String @default("")
51+
col32 String @default("")
52+
col33 String @default("")
53+
col34 String @default("")
54+
col35 String @default("")
55+
col36 String @default("")
56+
col37 String @default("")
57+
col38 String @default("")
58+
col39 String @default("")
59+
col40 String @default("")
60+
col41 String @default("")
61+
col42 String @default("")
62+
col43 String @default("")
63+
col44 String @default("")
64+
col45 String @default("")
65+
col46 String @default("")
66+
col47 String @default("")
67+
col48 String @default("")
68+
col49 String @default("")
69+
col50 String @default("")
70+
col51 String @default("")
71+
}
72+
`,
73+
{ usePrismaPush: true },
74+
);
75+
76+
await db.post.create({
77+
data: {
78+
opportunities: {
79+
create: [{}],
80+
},
81+
},
82+
});
83+
84+
// This should not throw PostgreSQL error 54023:
85+
// "cannot pass more than 100 arguments to a function"
86+
const result = await db.post.findMany({
87+
include: { opportunities: true },
88+
});
89+
90+
expect(result).toHaveLength(1);
91+
expect(result[0].opportunities).toHaveLength(1);
92+
});
93+
});

0 commit comments

Comments
 (0)