-
-
Notifications
You must be signed in to change notification settings - Fork 132
Description
Description
When using PostgreSQL with UUID primary keys (@db.Uuid) and access policies that reference auth().userId, the policy-check SQL fails at runtime with:
operator does not exist: text = uuid
Reproduction
ZModel schema
model User {
userId String @id @db.Uuid @default(uuid())
@@auth()
}
model Client {
clientId String @id @db.Uuid @default(uuid())
dealershipId String @db.Uuid
Dealership Dealership @relation(fields: [dealershipId], references: [dealershipId])
@@allow('all', Dealership.UserAccess?[userId == auth().userId])
}
model DealershipUserAccess {
dealershipId String @db.Uuid
userId String @db.Uuid
// ...relations
}What happens
auth().userIdis resolved as a JavaScript string value (e.g.,"550e8400-e29b-41d4-a716-446655440000")- The
valueMemberAccessmethod inexpression-transformer.tscallstransformValue(value, 'String')— because the ZModel type isString transformValuepasses the string through to the PostgreSQL dialect'stransformInput, which is a no-op for strings- Kysely creates a parameterized query binding the value as
$1with PostgreSQL typetext - The generated SQL compares this
textparameter against auuidcolumn:"DealershipUserAccess"."userId" = $1 - PostgreSQL raises
operator does not exist: text = uuidbecause there is no implicit cast fromtexttouuid
Root cause
In packages/plugins/policy/src/expression-transformer.ts, the transformValue method receives only the BuiltinType (e.g., 'String'). It does not have access to the field's native database type attribute (@db.Uuid), which is available in the FieldDef.attributes array as { name: "@db.Uuid" }.
The PostgreSQL dialect's transformInput method (in packages/orm/src/client/crud/dialects/postgresql.ts) has no handling for UUID values — it falls through to the default pass-through for strings.
Expected behavior
When a String field has @db.Uuid (or other native type attributes), the generated SQL should cast the parameter value appropriately. For example:
-- Current (broken):
"DealershipUserAccess"."userId" = $1 -- $1 is text, column is uuid → ERROR
-- Expected (working):
"DealershipUserAccess"."userId" = $1::uuid -- explicit castPotential fix approaches
-
Extend
transformValue/transformInputto accept native type info: Pass the@db.Uuidattribute information fromFieldDef.attributesthrough to the value transformation, and have the PostgreSQL dialect wrap the value in a::uuidcast usingsql.raworeb.cast(). -
Handle it at the binary comparison level: In
_binaryortransformAuthBinary, when one side is a column reference to a@db.Uuidfield and the other is a value, wrap the value with a cast.
Current workaround
Users can work around this by adding an implicit cast in a Prisma migration:
CREATE CAST (text AS uuid) WITH INOUT AS IMPLICIT;However, this is a global database-level change that may have unintended side effects.
Environment
- ZenStack: v3.3.3
- PostgreSQL: 16
- Prisma: 6.x
- Node.js: 22