Skip to content

Commit 82482da

Browse files
committed
fix: cast auth() string values to uuid on PostgreSQL when field has @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 #2394
1 parent 608133a commit 82482da

1 file changed

Lines changed: 26 additions & 2 deletions

File tree

packages/plugins/policy/src/expression-transformer.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
ReferenceNode,
4141
SelectionNode,
4242
SelectQueryNode,
43+
sql,
4344
TableNode,
4445
ValueListNode,
4546
ValueNode,
@@ -187,7 +188,8 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
187188
if (context.contextValue) {
188189
// if we're transforming against a value object, fields should be evaluated directly
189190
const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, expr.field);
190-
return this.transformValue(context.contextValue[expr.field], fieldDef.type as BuiltinType);
191+
const node = this.transformValue(context.contextValue[expr.field], fieldDef.type as BuiltinType);
192+
return this.applyNativeTypeCast(node, fieldDef);
191193
}
192194

193195
const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, expr.field);
@@ -802,15 +804,37 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
802804
curr = ValueNode.createImmediate(null);
803805
break;
804806
}
805-
currType = QueryUtils.requireField(this.schema, currType, field).type;
807+
const fieldDef = QueryUtils.requireField(this.schema, currType, field);
808+
currType = fieldDef.type;
806809
if (i === expr.members.length - 1) {
807810
// last segment (which is the value), make sure it's transformed
808811
curr = this.transformValue(curr, currType as BuiltinType);
812+
// apply native type cast if needed (e.g., @db.Uuid on PostgreSQL)
813+
curr = this.applyNativeTypeCast(curr, fieldDef);
809814
}
810815
}
811816
return curr;
812817
}
813818

819+
/**
820+
* Applies a native database type cast to a value node if needed.
821+
*
822+
* When policy expressions compare auth() member values against typed columns,
823+
* the parameterized values are sent with their JavaScript types (e.g., text for strings).
824+
* Some database-specific column types require explicit casting — for instance, PostgreSQL
825+
* raises "operator does not exist: text = uuid" when comparing a text parameter against
826+
* a uuid column. This method inspects the field's native type attributes (e.g., @db.Uuid)
827+
* and wraps the node with an appropriate SQL cast.
828+
*/
829+
private applyNativeTypeCast(node: OperationNode, fieldDef: FieldDef): OperationNode {
830+
if (this.schema.provider.type === 'postgresql' && fieldDef.attributes) {
831+
if (fieldDef.attributes.some((attr) => attr.name === '@db.Uuid')) {
832+
return sql`${new ExpressionWrapper(node)}::uuid`.toOperationNode();
833+
}
834+
}
835+
return node;
836+
}
837+
814838
private transformRelationAccess(
815839
field: string,
816840
relationModel: string,

0 commit comments

Comments
 (0)