fix: cast auth() string values to uuid on PostgreSQL when field has @db.Uuid#2396
fix: cast auth() string values to uuid on PostgreSQL when field has @db.Uuid#2396niehaus1301 wants to merge 1 commit intozenstackhq:devfrom
Conversation
…db.Uuid When policy expressions compare auth() member values against columns with @db.Uuid native type, PostgreSQL raises 'operator does not exist: text = uuid' because parameterized string values are sent as text type. This fix inspects the field's native type attributes from the runtime schema and applies an explicit ::uuid cast when needed. The cast is applied in two code paths: - valueMemberAccess: for auth().field member access in policies - _field with contextValue: for field evaluation in collection predicates Fixes zenstackhq#2394
📝 WalkthroughWalkthroughThis PR adds native database type casting support to policy expression evaluation, specifically handling PostgreSQL UUID casts. A new private helper method Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/plugins/policy/src/expression-transformer.ts`:
- Around line 829-836: The applyNativeTypeCast method currently always appends
"::uuid" for fields with the `@db.Uuid` attribute, which breaks when
fieldDef.array is true because arrays must be cast to "uuid[]"; update
applyNativeTypeCast to check fieldDef.array and, when provider is 'postgresql'
and the field has the `@db.Uuid` attribute, return sql`${new
ExpressionWrapper(node)}::uuid[]` for arrays and sql`${new
ExpressionWrapper(node)}::uuid` for scalars (preserving existing behavior for
non-array fields) so array values produced by buildArrayValue/transformValue are
cast correctly.
| private applyNativeTypeCast(node: OperationNode, fieldDef: FieldDef): OperationNode { | ||
| if (this.schema.provider.type === 'postgresql' && fieldDef.attributes) { | ||
| if (fieldDef.attributes.some((attr) => attr.name === '@db.Uuid')) { | ||
| return sql`${new ExpressionWrapper(node)}::uuid`.toOperationNode(); | ||
| } | ||
| } | ||
| return node; | ||
| } |
There was a problem hiding this comment.
Array UUID field (fieldDef.array) not checked — wrong cast type for array fields.
When a field is declared as String[] @db.Uuid``, transformValue returns an array node (via `buildArrayValue`). Wrapping it with `::uuid` produces `ARRAY[$1, $2]::uuid`, which PostgreSQL rejects — the correct cast is `::uuid[]`. The condition needs to branch on `fieldDef.array`.
🐛 Proposed fix
private applyNativeTypeCast(node: OperationNode, fieldDef: FieldDef): OperationNode {
if (this.schema.provider.type === 'postgresql' && fieldDef.attributes) {
if (fieldDef.attributes.some((attr) => attr.name === '@db.Uuid')) {
- return sql`${new ExpressionWrapper(node)}::uuid`.toOperationNode();
+ const castType = fieldDef.array ? 'uuid[]' : 'uuid';
+ return sql`${new ExpressionWrapper(node)}::${sql.raw(castType)}`.toOperationNode();
}
}
return node;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private applyNativeTypeCast(node: OperationNode, fieldDef: FieldDef): OperationNode { | |
| if (this.schema.provider.type === 'postgresql' && fieldDef.attributes) { | |
| if (fieldDef.attributes.some((attr) => attr.name === '@db.Uuid')) { | |
| return sql`${new ExpressionWrapper(node)}::uuid`.toOperationNode(); | |
| } | |
| } | |
| return node; | |
| } | |
| private applyNativeTypeCast(node: OperationNode, fieldDef: FieldDef): OperationNode { | |
| if (this.schema.provider.type === 'postgresql' && fieldDef.attributes) { | |
| if (fieldDef.attributes.some((attr) => attr.name === '@db.Uuid')) { | |
| const castType = fieldDef.array ? 'uuid[]' : 'uuid'; | |
| return sql`${new ExpressionWrapper(node)}::${sql.raw(castType)}`.toOperationNode(); | |
| } | |
| } | |
| return node; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/plugins/policy/src/expression-transformer.ts` around lines 829 -
836, The applyNativeTypeCast method currently always appends "::uuid" for fields
with the `@db.Uuid` attribute, which breaks when fieldDef.array is true because
arrays must be cast to "uuid[]"; update applyNativeTypeCast to check
fieldDef.array and, when provider is 'postgresql' and the field has the `@db.Uuid`
attribute, return sql`${new ExpressionWrapper(node)}::uuid[]` for arrays and
sql`${new ExpressionWrapper(node)}::uuid` for scalars (preserving existing
behavior for non-array fields) so array values produced by
buildArrayValue/transformValue are cast correctly.
ymc9
left a comment
There was a problem hiding this comment.
Hi @niehaus1301 , thanks for reporting the issue and making the PR.
The policy handling code is supposed to be db-type agnostic. I understand the problem now and will probably attempt a different fix at the db dialect side.
Problem
When using
auth()in access policies with PostgreSQL and the auth model has fields annotated with@db.Uuid, the policy check fails with:This happens because parameterized string values from
auth()are sent astexttype, but the corresponding database columns areuuidtype. PostgreSQL does not implicitly cast between these types.Reproduction
The generated SQL compares
auth().dealershipId(atextparameter) against thedealershipIdcolumn (uuidtype), which PostgreSQL rejects.Root Cause
In
expression-transformer.ts, thevalueMemberAccessmethod resolvesauth()member fields and callstransformValue(curr, currType)wherecurrTypeis the ZModel type ('String'). The PostgreSQL dialect'stransformInputforStringis a no-op, so the value remains a plain text parameter. When Kysely parameterizes this as$1, PostgreSQL inferstexttype, causing the type mismatch.The
@db.Uuidnative type attribute is available in the runtime schema viaFieldDef.attributes, but was not being utilized during value transformation.Fix
Added an
applyNativeTypeCastmethod that inspects the field's native type attributes and wraps the value node with an explicit::uuidSQL cast when:@db.UuidattributeThe cast is applied in two code paths:
valueMemberAccess: Forauth().fieldmember access in policies (the primary case)_fieldwithcontextValue: For field evaluation in collection predicates with value objectsChanges
Only one file is modified:
packages/plugins/policy/src/expression-transformer.tssqlto Kysely importsvalueMemberAccessto store the fullFieldDefand apply native type cast aftertransformValue_fieldwithcontextValueto also apply native type castapplyNativeTypeCasthelper with JSDoc documentationFixes #2394
Summary by CodeRabbit
Bug Fixes