From 8c73515aa094a78b1386a280d5d407764025b5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 16 Jun 2026 22:02:27 +0700 Subject: [PATCH 1/2] Fix Postgres text array cell edits --- .changeset/fix-postgres-text-array-edits.md | 5 +++ FEATURES.md | 1 + data/postgres-core/dml.test.ts | 32 ++++++++++++++++++ data/postgres-core/dml.ts | 2 ++ data/query.ts | 37 +++++++++++++++++++-- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-postgres-text-array-edits.md diff --git a/.changeset/fix-postgres-text-array-edits.md b/.changeset/fix-postgres-text-array-edits.md new file mode 100644 index 00000000..539b692d --- /dev/null +++ b/.changeset/fix-postgres-text-array-edits.md @@ -0,0 +1,5 @@ +--- +"@prisma/studio-core": patch +--- + +Fix PostgreSQL text array cell edits when queries are compiled with inline values. diff --git a/FEATURES.md b/FEATURES.md index 1a3f6af8..d18c0018 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -264,6 +264,7 @@ Exports can copy directly to the clipboard or save to disk, include column heade Editable cells open popover editors with datatype-specific controls for raw text, numeric, boolean, enum, JSON/array, date, and time values. Save/cancel keyboard behavior is standardized, and null/default/empty semantics are handled explicitly per input type. +Native PostgreSQL arrays can be edited from JSON-style array values and are written back with explicit array casts when inline SQL literals are required. PostgreSQL user-defined enum arrays also persist through that same staged-edit flow, with schema-qualified casts emitted in a form PostgreSQL accepts for `enum[]` writes. ## Staged Multi-Cell Editing diff --git a/data/postgres-core/dml.test.ts b/data/postgres-core/dml.test.ts index a87e6e96..4613a28b 100644 --- a/data/postgres-core/dml.test.ts +++ b/data/postgres-core/dml.test.ts @@ -1232,6 +1232,38 @@ describe("postgres-core/dml", () => { ]); }); + it("supports PostgreSQL text array updates when parameters are inlined", async () => { + const table = createSearchTypesTable(); + const query = getUpdateQuery( + { + changes: { arr_col: ["tag1", "tag2", "tag3"] }, + row: { id: "row_tr" }, + table, + }, + { noParameters: true }, + ); + + expect(query).toMatchInlineSnapshot(` + { + "parameters": [], + "sql": "update "public"."search_types" set "arr_col" = cast(array['tag1', 'tag2', 'tag3'] as text[]) where "id" = 'row_tr' returning "id", "str_col", "dt_col", "bool_col", "enum_col", "time_col", "raw_col", "num_col", "json_col", "arr_col", cast(floor(extract(epoch from now()) * 1000) as text) as "__ps_updated_at__"", + "transformations": undefined, + } + `); + + const [error] = await executor.execute(query); + + expect(error).toBeNull(); + + const persisted = await pglite.query<{ arr_col: string }>(` + select "arr_col"::text as "arr_col" + from "public"."search_types" + where "id" = 'row_tr' + `); + + expect(persisted.rows).toEqual([{ arr_col: "{tag1,tag2,tag3}" }]); + }); + it("casts PostgreSQL enum arrays with the array suffix outside the quoted user-defined type name", async () => { const table = createEnumArrayUsersTable(); const query = getUpdateQuery({ diff --git a/data/postgres-core/dml.ts b/data/postgres-core/dml.ts index c617f76a..7d1bd238 100644 --- a/data/postgres-core/dml.ts +++ b/data/postgres-core/dml.ts @@ -52,6 +52,7 @@ export function getInsertQuery( applyTransformations({ columns, context: "insert", + noParameters: requirements?.noParameters, supportsDefaultKeyword: true, values: rows, }), @@ -285,6 +286,7 @@ export function getUpdateQuery( applyTransformations({ columns, context: "update", + noParameters: requirements?.noParameters, supportsDefaultKeyword: true, values: changes, }), diff --git a/data/query.ts b/data/query.ts index 569a9aa2..b80800dc 100644 --- a/data/query.ts +++ b/data/query.ts @@ -278,6 +278,7 @@ function tupleFrom(items: unknown[]): Expression { export interface ApplyWriteTransformationsProps { columns: Table["columns"]; context: C; + noParameters?: boolean; values: C extends "update" ? Record : Record | Record[]; @@ -300,6 +301,7 @@ export function applyTransformations( interface TransformValuesProps { columns: Table["columns"]; context: "insert" | "update"; + noParameters?: boolean; supportsDefaultKeyword: boolean; values: Record; } @@ -331,7 +333,10 @@ function transformValues( return valueEntries.reduce( (obj, [key, value]) => ({ ...obj, - [key]: transformValue(value, columns[key]!, supportsDefaultKeyword), + [key]: transformValue(value, columns[key]!, { + inlineArrayValues: props.noParameters === true, + supportsDefaultKeyword, + }), }), requiredColumns.reduce((defaults, column) => { const { datatype, fkColumn, name } = column; @@ -371,9 +376,13 @@ function transformValues( function transformValue( value: unknown, column: Column, - supportsDefaultKeyword = true, + options: { + inlineArrayValues?: boolean; + supportsDefaultKeyword?: boolean; + } = {}, ): Expression { const { datatype, defaultValue, nullable } = column; + const { inlineArrayValues = false, supportsDefaultKeyword = true } = options; const eb = expressionBuilder(); @@ -385,6 +394,13 @@ function transformValue( return supportsDefaultKeyword ? sql`default` : eb.lit(null); } + if (inlineArrayValues && datatype.isArray && Array.isArray(value)) { + return eb.cast( + getArrayValueExpression(value), + getArrayTypeCastTarget(datatype), + ); + } + if (!datatype.isNative) { return eb.cast(eb.val(value), getUserDefinedTypeCastTarget(datatype)); } @@ -392,6 +408,23 @@ function transformValue( return eb.val(value); } +function getArrayValueExpression(value: unknown[]): Expression { + return sql`array[${sql.join( + value.map((item) => + Array.isArray(item) ? getArrayValueExpression(item) : sql`${item}`, + ), + sql`, `, + )}]`; +} + +function getArrayTypeCastTarget(datatype: DataType): Expression { + if (!datatype.isNative) { + return getUserDefinedTypeCastTarget(datatype); + } + + return sql.raw(datatype.name); +} + function getUserDefinedTypeCastTarget(datatype: DataType): Expression { const { isArray, name, schema } = datatype; From cd10a443506caf39790f66a2acc7a12ea9be09af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 16 Jun 2026 22:27:18 +0700 Subject: [PATCH 2/2] Add Postgres text array insert regression --- data/postgres-core/dml.test.ts | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/data/postgres-core/dml.test.ts b/data/postgres-core/dml.test.ts index 4613a28b..eae5c6a9 100644 --- a/data/postgres-core/dml.test.ts +++ b/data/postgres-core/dml.test.ts @@ -1264,6 +1264,42 @@ describe("postgres-core/dml", () => { expect(persisted.rows).toEqual([{ arr_col: "{tag1,tag2,tag3}" }]); }); + it("supports PostgreSQL text array inserts when parameters are inlined", async () => { + const table = createSearchTypesTable(); + const query = getInsertQuery( + { + rows: [ + { + arr_col: ["tag1", "tag2", "tag3"], + id: "row_insert_inline_arr", + }, + ], + table, + }, + { noParameters: true }, + ); + + expect(query).toMatchInlineSnapshot(` + { + "parameters": [], + "sql": "insert into "public"."search_types" ("id", "arr_col") values ('row_insert_inline_arr', cast(array['tag1', 'tag2', 'tag3'] as text[])) returning "id", "str_col", "dt_col", "bool_col", "enum_col", "time_col", "raw_col", "num_col", "json_col", "arr_col", cast(floor(extract(epoch from now()) * 1000) as text) as "__ps_inserted_at__"", + "transformations": undefined, + } + `); + + const [error] = await executor.execute(query); + + expect(error).toBeNull(); + + const persisted = await pglite.query<{ arr_col: string }>(` + select "arr_col"::text as "arr_col" + from "public"."search_types" + where "id" = 'row_insert_inline_arr' + `); + + expect(persisted.rows).toEqual([{ arr_col: "{tag1,tag2,tag3}" }]); + }); + it("casts PostgreSQL enum arrays with the array suffix outside the quoted user-defined type name", async () => { const table = createEnumArrayUsersTable(); const query = getUpdateQuery({