Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions packages/orm/src/client/crud/dialects/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getDelegateDescendantModels,
getManyToManyRelation,
isRelationField,
isTypeDef,
requireField,
requireIdFields,
requireModel,
Expand Down Expand Up @@ -52,13 +53,25 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
invariant(false, 'should not reach here: AnyNull is not a valid input value');
}

if (Array.isArray(value)) {
// node-pg incorrectly handles array values passed to non-array JSON fields,
// the workaround is to JSON stringify the value
// https://github.com/brianc/node-postgres/issues/374

if (isTypeDef(this.schema, type)) {
// type-def fields (regardless array or scalar) are stored as scalar `Json` and
// their input values need to be stringified if not already (i.e., provided in
// default values)
if (typeof value !== 'string') {
return JSON.stringify(value);
} else {
return value;
}
} else if (Array.isArray(value)) {
if (type === 'Json' && !forArrayField) {
// node-pg incorrectly handles array values passed to non-array JSON fields,
// the workaround is to JSON stringify the value
// https://github.com/brianc/node-postgres/issues/374
// scalar `Json` fields need their input stringified
return JSON.stringify(value);
} else {
// `Json[]` fields need their input as array (not stringified)
return value.map((v) => this.transformPrimitive(v, type, false));
}
} else {
Expand Down
94 changes: 94 additions & 0 deletions tests/regression/test/issue-493.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { createTestClient } from '@zenstackhq/testtools';
import { describe, expect, it } from 'vitest';

describe('Issue 493 regression tests', () => {
it('should correctly handle JSON and typed-JSON array fields for PostgreSQL', async () => {
const schema = `
type InlineButton {
id String
text String
callback_data String?
url String?
message String?
type String?
}

type BotButton {
id String
label String
action String
enabled Boolean
order_index Int
message String
inline_buttons InlineButton[]? // Nested custom type
}

model bot_settings {
id Int @id @default(autoincrement())
setting_key String @unique
menu_buttons BotButton[] @json // Array of custom type
meta Meta @json
}

type Meta {
info String
}

model Foo {
id Int @id @default(autoincrement())
data Json
}
`;

const db = await createTestClient(schema, { provider: 'postgresql', debug: true });

// plain JSON non-array
await expect(
db.foo.create({
data: {
data: { hello: 'world' },
},
}),
).resolves.toMatchObject({
data: { hello: 'world' },
});

// plain JSON array
await expect(
db.foo.create({
data: {
data: [{ hello: 'world' }],
},
}),
).resolves.toMatchObject({
data: [{ hello: 'world' }],
});

// typed-JSON array & non-array
const input = {
setting_key: 'abc',
menu_buttons: [
{
id: '1',
label: 'Button 1',
action: 'action_1',
enabled: true,
order_index: 1,
message: 'msg',
inline_buttons: [
{
id: 'ib1',
text: 'Inline 1',
},
],
},
],
meta: { info: 'some info' },
};
await expect(
db.bot_settings.create({
data: input,
}),
).resolves.toMatchObject(input);
});
});
Loading