Přeskočit na obsah

Prisma $queryRawUnsafe — uuid vs. text bez explicitního castu (42804/42883)

import { Aside } from ‘@astrojs/starlight/components’;

Tři na pohled nesouvisející rozbité featury v produkčním SaaS:

  1. Archiv generovaných XML dokumentů zůstával trvale prázdný, přestože generování vracelo 200.
  2. AI vytěžení dokumentu (OCR) vracelo 500 „Něco se pokazilo”, přestože samotná AI extrakce proběhla úspěšně.
  3. Generování PDF padalo na 500 — ale jen u organizací bez vyplněného firemního profilu.

V PM2 lozích se opakovaly Postgres chyby 42804 a 42883:

ERROR: column "generated_by" is of type uuid but expression is of type text
ERROR: operator does not exist: uuid = text
ERROR: operator does not exist: text = uuid

Prisma binduje parametry raw dotazů jako text. Postgres je v INSERT ... VALUES kontextu umí implicitně přetypovat podle cílového sloupce, ale v WHERE col = $1 a u typovaných výrazů NE:

-- padá (uuid = text):
UPDATE tenant_x.records SET status = 'done' WHERE id = $1
-- padá (sloupec uuid, výraz text):
INSERT INTO tenant_x.archive (..., created_by) VALUES (..., $6)

Záludný je i opačný směr: tabulka s text/cuid primárním klíčem (typicky Prisma @id @default(cuid())) a dotaz s nadbytečným castem:

-- padá (text = uuid), protože id je cuid TEXT:
SELECT * FROM public.organizations WHERE id = $1::uuid

Smrtící kombinace: handler chybu z archivačního kroku spolkl (catch { console.error(...) }) a vrátil 200 → feature byla měsíce tiše rozbitá a nikdo si nevšiml.

Explicitní casty podle skutečného typu sloupce:

-- před
UPDATE tenant_x.records SET status = 'done' WHERE id = $1
-- po
UPDATE tenant_x.records SET status = 'done' WHERE id = $1::uuid
-- před (column "created_by" is of type uuid...)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
-- po
VALUES ($1, $2, $3, $4, $5, $6::uuid, $7, $8)
-- před (public tabulka má TEXT cuid PK!)
SELECT * FROM public.organizations WHERE id = $1::uuid
-- po
SELECT * FROM public.organizations WHERE id = $1
  • E2E test, který ověřuje výsledek operace (řádek v archivu, stažené PDF), ne jen HTTP 200 hlavního kroku.
  • Grep na rizikový vzor: grep -rn 'WHERE id = \$[0-9]' src/ | grep RawUnsafe a zkontrolovat typ PK každé tabulky.
  • V multi-tenant setupu pozor: tenant tabulky mívají uuid PK (gen_random_uuid()), public/Prisma tabulky text cuid — stejný dotazový vzor potřebuje jiný cast.
  1. Implicitní koerce v INSERT maskuje problém — projde insert, spadne až UPDATE/WHERE na stejné tabulce.
  2. Prázdný/log-only catch kolem vedlejších efektů = tiše rozbitá feature; lepší je propagovat chybu nebo aspoň alertovat.
  3. Po každé změně typů PK projít všechny raw dotazy na tabulku.
Přidal aiarchitekt.cz · 11. 6. 2026 2:00
Provozuje aiarchitekt.cz