From 1148f2ec5e677726690c501c2e9daa8b7a5aae15 Mon Sep 17 00:00:00 2001 From: Krist Ponpairin Date: Fri, 23 Oct 2026 02:35:40 +0700 Subject: [PATCH] fix(policy): inline @computed fields referenced in @@allow/@@deny instead of a bare column ref ExpressionTransformer.createColumnRef built a plain "Model"."field" reference for every non-relation field, including @computed ones, which don't physically exist as columns. Delegate to the dialect's existing fieldRef() (already used by every other query path) when the field is computed, same as the rest of the ORM does. Fixes #2888 --- .../policy/src/expression-transformer.ts | 11 ++++++ tests/regression/test/issue-2888.test.ts | 34 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/regression/test/issue-2888.test.ts diff --git a/packages/plugins/policy/src/expression-transformer.ts b/packages/plugins/policy/src/expression-transformer.ts index ea5d0f1ee..61d2509c5 100644 --- a/packages/plugins/policy/src/expression-transformer.ts +++ b/packages/plugins/policy/src/expression-transformer.ts @@ -1130,6 +1130,17 @@ export class ExpressionTransformer { const tableName = context.alias ?? context.modelOrType; const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, column); + + if (fieldDef.computed) { + // `@computed` fields don't physically exist as columns — inline the + // computed expression the same way every other query path does via + // `dialect.fieldRef()`, instead of emitting a bare (nonexistent) column ref. + if (fieldDef.params) { + throw new Error(`parameterized computed field "${column}" cannot be used in a policy expression`); + } + return this.dialect.fieldRef(context.modelOrType, column, tableName).toOperationNode(); + } + if (!fieldDef.originModel || fieldDef.originModel === context.modelOrType) { return ReferenceNode.create(ColumnNode.create(column), TableNode.create(tableName)); } diff --git a/tests/regression/test/issue-2888.test.ts b/tests/regression/test/issue-2888.test.ts new file mode 100644 index 000000000..aaa31a088 --- /dev/null +++ b/tests/regression/test/issue-2888.test.ts @@ -0,0 +1,34 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue 2888', () => { + it('a computed field referenced in @@allow does not throw "no such column" and evaluates its expression', async () => { + const db = await createPolicyTestClient( + ` +model Model { + id Int @id @default(autoincrement()) + x Int + canEdit Boolean @computed + + @@allow('create,read', true) + @@allow('update', canEdit) +} + `, + { + provider: 'mysql', + usePrismaPush: true, + computedFields: { + model: { + canEdit: (eb: any) => eb.lit(true), + }, + }, + } as any, + ); + + await db.model.create({ data: { id: 1, x: 1 } }); + + await expect(db.model.update({ where: { id: 1 }, data: { x: 2 } })).resolves.toMatchObject({ + x: 2, + }); + }); +});