Skip to content

@computed fields used in @@allow/@@deny policy expressions compile to a bare (nonexistent) column ref #2888

Closed

Description

@krist7599555

A @computed field referenced from its own model's @@allow/@@deny policy expression compiles to a plain "Model"."field" column reference, which doesn't physically exist in the database — the write fails with "Unknown column" / "no such column" instead of evaluating the computed expression.

Repro

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) => eb.lit(true) } },
  },
);

await db.model.create({ data: { id: 1, x: 1 } });
await db.model.update({ where: { id: 1 }, data: { x: 2 } }); // throws

Throws (MySQL):

Unknown column 'Model.canEdit' in 'where clause'
sql: 'update `Model` set `x` = ? where `Model`.`id` = ? and `Model`.`canEdit` '

Not dialect-specific — every other query path (plain select/where/orderBy) already inlines @computed fields correctly via BaseCrudDialect.fieldRef(); the policy expression transformer is the one place that doesn't.

Root cause

ExpressionTransformer.createColumnRef (packages/plugins/policy/src/expression-transformer.ts) builds a bare column ref for every non-relation field, with no check for fieldDef.computed:

private createColumnRef(column: string, context: ExpressionTransformerContext) {
    const tableName = context.alias ?? context.modelOrType;
    const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, column);
    if (!fieldDef.originModel || fieldDef.originModel === context.modelOrType) {
        return ReferenceNode.create(ColumnNode.create(column), TableNode.create(tableName)); // <-- bug
    }
    return this.buildDelegateBaseFieldSelect(...);
}

The transformer already has this.dialect (a BaseCrudDialect instance, built at construction and used elsewhere in this same file) whose fieldRef(model, field, alias) correctly inlines @computed fields everywhere else — createColumnRef just never delegates to it.

Fix

When the referenced field is computed (and not parameterized — a parameterized computed field needs query-time args a static policy expression can't supply), delegate to this.dialect.fieldRef() and bridge its Expression back to an OperationNode:

  private createColumnRef(column: string, context: ExpressionTransformerContext) {
      const tableName = context.alias ?? context.modelOrType;
      const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, column);
+
+     if (fieldDef.computed) {
+         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));
      }
      return this.buildDelegateBaseFieldSelect(...);
  }

PR incoming.

Scope

This only fixes a computed field referenced from its own model's policy. A computed field reached by traversing a relation (e.g. a SubmissionScore policy referencing submission.someComputedField) goes through a different code path (transformRelationAccess) and is not covered by this fix — a separate, still-open gap.

Environment

Activity

  1. ymc9 commented on Nov 6, 2026

    @ymc9
    Member

    Fixed in v3.10.1 by #2889 and #2910.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions