#30488·Prisma

SQL builder: no insert whose rows come from a select, so a data migration that derives rows must be raw SQL

Author: wmadden-electricCreated Sep 29, 2026Updated Sep 29, 2026
Labelskind/bug

What happened

A data migration needs to create rows from rows that already exist: INSERT INTO actor (kind, "contactId") SELECT DISTINCT 'human', "provenanceActorId" FROM ask WHERE .... The typed SQL builder cannot express it. insert takes only literal rows, so the only way to write this inside this.dataTransform is raw SQL, which is not checked against the contract.

Reproduction

Versions: @prisma/orm-postgres 8.0.0-rc.12. Checked against the source of main at d136137 (2026-09-28).

typescript
const { sql } = postgres<Contract>({ contractJson });

sql.public.actor.insert(
  sql.public.ask
    .select('contactId', (f) => f.provenanceActorId)
    .where((f, fns) => fns.eq(f.provenanceActorKind, 'human'))
    .distinct(),
);

tsc fails: TS2345: Argument of type 'SelectQuery<...>' is not assignable to parameter of type 'readonly { ... }[]'.

Cause

  • packages/2-sql/4-lanes/sql-builder/src/types/table-proxy.ts: insert(rows: ReadonlyArray<...>) accepts literal rows only.
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts, InsertAst: holds rows: ReadonlyArray<Record<string, InsertValue>>. The AST has no insert whose source is a select.

What was expected

An insert whose rows come from a select over the same contract, typed so that the select's columns must match the target's columns. Column defaults the contract declares (for example @default(uuid(7))) need a decision: either the insert-from-select applies them per row in SQL, or the select must supply the column.

Why it matters

The migration guide says a data change belongs in a typed dataTransform, because migrations form a graph and only a typed transform is checked against the contract. Any migration that introduces a table derived from existing data, such as a lookup table extracted from text columns, cannot be written that way today.