Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Create policy compares stored enum array with text[] candidate input (PostgreSQL 42883)

Abierto
#2,866 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
54/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
postgresql, typescript

Línea de trabajo

Start with the create-policy preflight shown in the issue and inspect how the virtual create row gets its enum-array type; the reported SQL casts the candidate to text[]. Run the supplied schema.zmodel and repro.ts against PostgreSQL to confirm the 42883 error. Done when a matching array passes policy, a different array is rejected, and equality preserves array order and multiplicity.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Create-policy equality between a stored enum array and a candidate create array produces a PostgreSQL enum[] = text[] comparison in ZenStack 3.9.7. A matching list should pass the policy; it instead raises PostgreSQL 42883. The create-input virtual row casts the enum field to text[].

This appears distinct from #2818: the enum below has no mappings, and ordinary enum-array reads/writes work. The failure is in the create-policy preflight comparison.

Environment

  • @zenstackhq/cli, orm, plugin-policy and schema: 3.9.7
  • PostgreSQL 16 (pgvector/pgvector:pg16); the application regression also reproduces against PGlite 0.4.1
  • Kysely 0.29.6, pg 8.13.1+, Node.js 22.23.3, pnpm 10.18.2

Reproduction

Save this as schema.zmodel, set DATABASE_URL to a PostgreSQL connection, and run pnpm exec zen generate:

datasource db {
  provider = "postgresql"
  url = env("DATABASE_URL")
}
enum Permission {
  VIEW
  EDIT
}
model Anchor {
  id Int @id
  approvals Approval[]
  roles Role[]
}
model Approval {
  id Int @id
  anchorId Int
  anchor Anchor @relation(fields: [anchorId], references: [id])
  permissions Permission[]
}
model Role {
  id Int @id
  anchorId Int
  anchor Anchor @relation(fields: [anchorId], references: [id])
  permissions Permission[]
  @@allow('read', true)
  @@allow('create', anchor.approvals?[permissions == this.permissions])
}

plugin policy {
  provider = "@zenstackhq/plugin-policy"
}

The following repro.ts creates and drops its own isolated database. Set TEST_POSTGRES_URL to an admin connection with CREATEDB, then run pnpm exec tsx repro.ts (with "type": "module" in package.json). The DDL is included to avoid relying on migration state.

import { randomUUID } from 'node:crypto';
import { ZenStackClient } from '@zenstackhq/orm';
import { PolicyPlugin } from '@zenstackhq/plugin-policy';
import { PostgresDialect } from 'kysely';
import { Pool } from 'pg';
import { schema } from './schema';

const admin = new Pool({ connectionString: process.env.TEST_POSTGRES_URL, max: 1 });
const name = `enum_array_repro_${randomUUID().replaceAll('-', '')}`;
await admin.query(`CREATE DATABASE "${name}"`);
const url = new URL(process.env.TEST_POSTGRES_URL!);
url.pathname = `/${name}`;
const pool = new Pool({ connectionString: url.toString(), max: 1 });
try {
  await pool.query(`
    CREATE TYPE "Permission" AS ENUM ('VIEW', 'EDIT');
    CREATE TABLE "Anchor" (id integer PRIMARY KEY);
    CREATE TABLE "Approval" (id integer PRIMARY KEY, "anchorId" integer REFERENCES "Anchor"(id), permissions "Permission"[] NOT NULL);
    CREATE TABLE "Role" (id integer PRIMARY KEY, "anchorId" integer REFERENCES "Anchor"(id), permissions "Permission"[] NOT NULL);
  `);
  const raw = new ZenStackClient(schema, { dialect: new PostgresDialect({ pool }) });
  await raw.anchor.create({ data: { id: 1 } });
  await raw.approval.create({ data: { id: 1, anchorId: 1, permissions: ['VIEW'] } });
  const db = raw.$use(new PolicyPlugin());
  try {
    await db.role.create({ data: { id: 1, anchorId: 1, permissions: ['VIEW'] } });
    console.log('Unexpected success');
    process.exitCode = 1;
  } catch (error: any) {
    console.log(JSON.stringify({ reason: error.reason, code: error.dbErrorCode, message: error.dbErrorMessage, sql: error.sql, sqlParams: error.sqlParams }, null, 2));
    if (error.dbErrorCode !== '42883') process.exitCode = 1;
  }
} finally {
  await pool.end();
  await admin.query(`DROP DATABASE "${name}" WITH (FORCE)`);
  await admin.end();
}

Observed result

{
  "reason": "db-query-error",
  "code": "42883",
  "message": "operator does not exist: \"Permission\"[] = text[]",
  "sql": "select exists (select 1 as \"_\" from (select cast(\"$values\".\"column1\" as integer) as \"id\", cast(\"$values\".\"column2\" as integer) as \"anchorId\", cast(\"$values\".\"column3\" as text[]) as \"permissions\" from (VALUES ($1, $2, cast(ARRAY[$3] as text[]))) as \"$values\") as \"Role\" where (select exists (select 1 as \"_\" from \"public\".\"Approval\" as \"$$t2\" where \"$$t1\".\"id\" = \"$$t2\".\"anchorId\" and \"$$t2\".\"permissions\" = \"Role\".\"permissions\") as \"_\" from \"public\".\"Anchor\" as \"$$t1\" where \"Role\".\"anchorId\" = \"$$t1\".\"id\")) as \"$condition\"",
  "sqlParams": [
    1,
    1,
    "VIEW"
  ]
}

Expected result

The matching ['VIEW'] create succeeds; a different array is rejected by policy. The candidate enum array should retain the database enum-array type, or both operands should be coerced consistently without losing array order/multiplicity semantics.

Current workaround

A custom policy-function expression converts the stored array and validated candidate values to JSONB and compares them exactly. Expanding the enum into membership/non-membership pairs also avoids the type error, but compares sets rather than preserving order and duplicate entries. Neither workaround changes the underlying enum-array equality limitation.

Lenguaje dominante
TypeScript
Estrellas
2.9k
Forks
157
Merge medio
2 d 46 min
PR fusionados (30 d)
17

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de zenstackhq/zenstack

Todos los issues de zenstackhq/zenstack

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.