Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#2,866 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
54/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
postgresql, typescript

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
TypeScript
スター
2.9k
フォーク
157
平均マージ
11時間 42分
マージ済み PR(30日)
20

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

zenstackhq/zenstack のほかの issue

zenstackhq/zenstack の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。