Skip to content

Commit 5ac9e13

Browse files
authored
fix(orm): properly stringify typed-json values for postgres (#498)
1 parent 0d74a02 commit 5ac9e13

File tree

2 files changed

+111
-4
lines changed

2 files changed

+111
-4
lines changed

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
getDelegateDescendantModels,
2323
getManyToManyRelation,
2424
isRelationField,
25+
isTypeDef,
2526
requireField,
2627
requireIdFields,
2728
requireModel,
@@ -52,13 +53,25 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
5253
invariant(false, 'should not reach here: AnyNull is not a valid input value');
5354
}
5455

55-
if (Array.isArray(value)) {
56+
// node-pg incorrectly handles array values passed to non-array JSON fields,
57+
// the workaround is to JSON stringify the value
58+
// https://github.com/brianc/node-postgres/issues/374
59+
60+
if (isTypeDef(this.schema, type)) {
61+
// type-def fields (regardless array or scalar) are stored as scalar `Json` and
62+
// their input values need to be stringified if not already (i.e., provided in
63+
// default values)
64+
if (typeof value !== 'string') {
65+
return JSON.stringify(value);
66+
} else {
67+
return value;
68+
}
69+
} else if (Array.isArray(value)) {
5670
if (type === 'Json' && !forArrayField) {
57-
// node-pg incorrectly handles array values passed to non-array JSON fields,
58-
// the workaround is to JSON stringify the value
59-
// https://github.com/brianc/node-postgres/issues/374
71+
// scalar `Json` fields need their input stringified
6072
return JSON.stringify(value);
6173
} else {
74+
// `Json[]` fields need their input as array (not stringified)
6275
return value.map((v) => this.transformPrimitive(v, type, false));
6376
}
6477
} else {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
describe('Issue 493 regression tests', () => {
5+
it('should correctly handle JSON and typed-JSON array fields for PostgreSQL', async () => {
6+
const schema = `
7+
type InlineButton {
8+
id String
9+
text String
10+
callback_data String?
11+
url String?
12+
message String?
13+
type String?
14+
}
15+
16+
type BotButton {
17+
id String
18+
label String
19+
action String
20+
enabled Boolean
21+
order_index Int
22+
message String
23+
inline_buttons InlineButton[]? // Nested custom type
24+
}
25+
26+
model bot_settings {
27+
id Int @id @default(autoincrement())
28+
setting_key String @unique
29+
menu_buttons BotButton[] @json // Array of custom type
30+
meta Meta @json
31+
}
32+
33+
type Meta {
34+
info String
35+
}
36+
37+
model Foo {
38+
id Int @id @default(autoincrement())
39+
data Json
40+
}
41+
`;
42+
43+
const db = await createTestClient(schema, { provider: 'postgresql', debug: true });
44+
45+
// plain JSON non-array
46+
await expect(
47+
db.foo.create({
48+
data: {
49+
data: { hello: 'world' },
50+
},
51+
}),
52+
).resolves.toMatchObject({
53+
data: { hello: 'world' },
54+
});
55+
56+
// plain JSON array
57+
await expect(
58+
db.foo.create({
59+
data: {
60+
data: [{ hello: 'world' }],
61+
},
62+
}),
63+
).resolves.toMatchObject({
64+
data: [{ hello: 'world' }],
65+
});
66+
67+
// typed-JSON array & non-array
68+
const input = {
69+
setting_key: 'abc',
70+
menu_buttons: [
71+
{
72+
id: '1',
73+
label: 'Button 1',
74+
action: 'action_1',
75+
enabled: true,
76+
order_index: 1,
77+
message: 'msg',
78+
inline_buttons: [
79+
{
80+
id: 'ib1',
81+
text: 'Inline 1',
82+
},
83+
],
84+
},
85+
],
86+
meta: { info: 'some info' },
87+
};
88+
await expect(
89+
db.bot_settings.create({
90+
data: input,
91+
}),
92+
).resolves.toMatchObject(input);
93+
});
94+
});

0 commit comments

Comments
 (0)