Create policy compares stored enum array with text[] candidate input (PostgreSQL 42883)
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
- Área
- authorization, databases
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
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de zenstackhq/zenstack
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
zenstackhq/zenstack#2873 ·
Los mantenedores suelen responder en 1 día
-
`@default` on a field of a `@map`-ed enum emits the enum name as the column defaultPosiblemente ocupada @jkugsiya la tomó hace 4 días. Abiertoruntime
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
zenstackhq/zenstack#2868 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
zenstackhq/zenstack#2694 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 68/100
zenstackhq/zenstack#2659 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
zenstackhq/zenstack#2542 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de zenstackhq/zenstack
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
polarsource/polar#15580 ·
Los mantenedores suelen responder en 1 día
-
accepted bug via-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 69/100
pingdotgg/t3code#20931 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug/1-unconfirmed kind/bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 83/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
ranxianglei/billion-context#3583 ·
Los mantenedores suelen responder en 1 día