fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: isEmpty function when using postgresql dialect - #2703

Merged
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Jun 12, 2026
Merged

fix: isEmpty function when using postgresql dialect#2703
ymc9 merged 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect

Conversation

@sanny-io

@sanny-iosanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL array length operations to explicitly specify dimension parameters for correct SQL generation.
  • Tests

    • Added comprehensive end-to-end test for PostgreSQL policy behavior with isEmpty function in deny rules.

@coderabbitai

coderabbitaiBot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

📝 Walkthrough

Walkthrough

This PR fixes PostgreSQL's array_length function call to include an explicit dimension parameter. The dialect change is validated by a new e2e test that verifies isEmpty in a deny policy operates correctly with Postgres.

Changes

PostgreSQL Array Length Dimension

Layer / File(s)Summary
Postgres dialect: array_length explicit dimension
packages/orm/src/client/crud/dialects/postgresql.ts
buildArrayLength now generates array_length(array, 1) with an explicit dimension index.
E2E test for isEmpty in deny policies
tests/e2e/orm/policy/isempty-function-pg.test.ts
Validates that a policy using isEmpty(roles) in a @@deny('create') rule executes without error on PostgreSQL.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through SQL arrays true,
With dimensions explicit—one, that's the clue!
isEmpty checks pass the policy gate,
Postgres runs smooth, the tests validate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing the isEmpty function for PostgreSQL dialect by correcting array_length to include the required dimension parameter.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanny-io

Copy link
Copy Markdown
ContributorAuthor

Context: isEmpty straight up did not work on postgres. array_lengthrequires two parameters, the second is the dimension of the array. Previously it would result in an error saying function array_length(text[]) does not exist

For in expressions, see below.

https://discord.com/channels/1035538056146595961/1513844077383258122/1513844077383258122

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

415-422: ⚡ Quick win

Remove commented-out dead code.

The commented-out enum handling block (lines 417-419) is now dead code since the logic below handles all cases including enums. Leaving commented-out code creates confusion about intent and clutters the codebase.

♻️ Proposed cleanup
 override buildArrayValue(values: Expression<unknown>[], elemType: string): AliasableExpression<unknown> {
const arr = sql`ARRAY[${sql.join(values, sql.raw(','))}]`;
- // if (isEnum(this.schema, elemType)) {- // return this.eb.cast(arr, sql`${sql.id(elemType)}[]`);- // }
const mappedType = this.getSqlType(elemType);
return this.eb.cast(arr, sql`${sql.id(mappedType)}[]`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 415 - 422,
Remove the commented-out enum handling inside buildArrayValue: delete the
commented lines that call isEnum(this.schema, elemType) and the associated
eb.cast fallback so the function only contains the active implementation using
getSqlType and eb.cast; leave buildArrayValue, getSqlType and eb.cast unchanged
and ensure no other references to the removed commented code remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 415-422: Remove the commented-out enum handling inside
buildArrayValue: delete the commented lines that call isEnum(this.schema,
elemType) and the associated eb.cast fallback so the function only contains the
active implementation using getSqlType and eb.cast; leave buildArrayValue,
getSqlType and eb.cast unchanged and ensure no other references to the removed
commented code remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dd49a34-6242-43f4-99d0-766ff6cbed89

📥 Commits

Reviewing files that changed from the base of the PR and between a2f1134 and 88a75a0.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts

@sanny-io
sanny-io marked this pull request as draft June 9, 2026 15:49
@sanny-iosanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialectfix: isEmpty function when using postgresql dialectJun 9, 2026
@sanny-io

Copy link
Copy Markdown
ContributorAuthor

I believe the issue may only happen when using access policies, so I have relocated the test.

@sanny-io
sanny-io marked this pull request as ready for review June 9, 2026 17:40

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/orm/policy/isempty-function-pg.test.ts (1)

4-36: ⚡ Quick win

Consider adding test cases for edge conditions.

The current test validates that isEmpty(roles) works when the array contains one element. For more comprehensive coverage, consider adding test cases for:

  1. Empty array: Verify that creating a user with roles: [] is denied by the policy
  2. Multiple roles: Confirm behavior with roles: ['AUTHOR', 'EDITOR']
  3. Implicit vs explicit behavior: Document whether the test relies on @@allow('all', true) to handle the non-empty case, or if the deny-only policy is sufficient

These additions would help prevent regressions and clarify the expected policy behavior.

📋 Suggested additional test cases
it('denies creation when roles is empty',async()=>{constschema=`model User { id Int `@id` `@default`(autoincrement()) roles Role[] @@allow('all', true) @@deny('create', isEmpty(roles))}enum Role { AUTHOR EDITOR} `;constclient=awaitcreatePolicyTestClient(schema,{usePrismaPush: true,provider: 'postgresql',});awaitexpect(client.user.create({data: {id: 2,roles: [],},})).rejects.toThrow();});it('allows creation with multiple roles',async()=>{// ... test with roles: ['AUTHOR', 'EDITOR']});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts` around lines 4 - 36, Add
edge-case tests around the isEmpty(roles) policy: add an it block that
constructs the same schema (with @@deny('create', isEmpty(roles))) and asserts
that client.user.create with roles: [] is rejected, and add another it block
that asserts client.user.create with roles: ['AUTHOR','EDITOR'] resolves and
returns the created user; use the same createPolicyTestClient call and
client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 502-504: Add an e2e test for Postgres that verifies enum-array
membership operators work when enums are mapped to text (so the cast to text[]
in buildArrayFilter preserves semantics): create a schema with enum Role and
field roles Role[], insert records with various role combinations, then run
queries using the array filters has, hasSome, and hasEvery against roles and
assert expected matches for provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.
---
Nitpick comments:
In `@tests/e2e/orm/policy/isempty-function-pg.test.ts`:
- Around line 4-36: Add edge-case tests around the isEmpty(roles) policy: add an
it block that constructs the same schema (with @@deny('create', isEmpty(roles)))
and asserts that client.user.create with roles: [] is rejected, and add another
it block that asserts client.user.create with roles: ['AUTHOR','EDITOR']
resolves and returns the created user; use the same createPolicyTestClient call
and client.user.create calls already present in the file (referencing
createPolicyTestClient and client.user.create) and keep the existing schema (or
add a variant that omits @@allow('all', true) if you want to assert behavior
without the allow rule) so tests cover empty-array denial and multi-role
acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ea969d-e58f-4475-8cc2-ebc2a46f0133

📥 Commits

Reviewing files that changed from the base of the PR and between 88a75a0 and 3e5e76f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • tests/e2e/orm/policy/isempty-function-pg.test.ts
🛑 Comments failed to post (1)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

502-504: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Search for enum-related tests, especially with arrays
rg -n -C5 --type=ts 'enum.*\[\]|Role\[\]|\.create.*roles:' tests/e2e/
# Search for enum usage in dialect code
ast-grep --pattern $'isEnum($$$) { $$$}'

Repository: zenstackhq/zenstack

Length of output: 5342


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Inspect getSqlType around the reported lines
FILE="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== getSqlType context =="
python3 - <<'PY'import itertools,syspath="packages/orm/src/client/crud/dialects/postgresql.ts"start=470; end=530with open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start <= i <= end: print(f"{i}:{line.rstrip()}") if i > end: breakPY# 2) Find actual PG enum schema definitions/usages in e2e testsecho"== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ ||trueecho"== e2e: enum array field patterns =="
rg -n --hidden --no-ignore -S --type=ts "[A-Za-z0-9_]+\\s*\\[\\]" tests/e2e/ -g'*.test.ts'| rg -n -S "enum|Enum"||trueecho"== e2e: postgres enum keywords =="
rg -n --hidden --no-ignore -S --type=ts "postgres.*enum|pg.*enum|::.*\\w+\\[\\]" tests/e2e/ ||true# 3) Search for enum usage tied to postgresql dialectecho"== code: isEnum usages (broader) =="
rg -n "isEnum\\(" packages/orm/src/client/crud/dialects/postgresql.ts packages/orm/src/client/crud/dialects -S ||true

Repository: zenstackhq/zenstack

Length of output: 6401


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Inspect the enum-array test that we know exists
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} =="
nl -ba "$FILE"| sed -n '1,220p'# Find all tests mentioning Role[] (enum array) in e2eecho"== rg 'Role\\[\\]' in e2e =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ ||true# Find tests that define enums and then use them as arrays (simple heuristic)echo"== rg: enum ... then later ... [] usage (limited window) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S | head -n 50
# Inspect typing/typecheck.ts around enum array mention
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"echo"== ${FILE2} (around enum array) =="
nl -ba "$FILE2"| sed -n '60,120p'# Search for isEmpty implementation usage (best-effort; may be TS in repo)echo"== code search: isEmpty( ... ) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src tests/e2e/orm | head -n 50

Repository: zenstackhq/zenstack

Length of output: 179


🏁 Script executed:

#!/bin/bashset -euo pipefail
FILE="tests/e2e/orm/policy/isempty-function-pg.test.ts"echo"== ${FILE} (first 200 lines with numbers) =="
cat -n "$FILE"| sed -n '1,220p'echo"== rg 'Role\\[\\]' in tests/e2e (first matches) =="
rg -n --type=ts "Role\\[\\]" tests/e2e/ | head -n 50 ||trueecho"== rg for enum definitions in e2e (first 100 lines of matches) =="
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm | head -n 100
echo"== typing/typecheck.ts around 'enum array' =="
FILE2="tests/e2e/orm/schemas/typing/typecheck.ts"
cat -n "$FILE2"| sed -n '60,130p'echo"== code search: isEmpty implementation (best-effort) =="
rg -n --type=ts "isEmpty\\(" packages/orm/src | head -n 80 ||true
rg -n --type=ts "function\\s+isEmpty|isEmpty\\s*:" packages/orm/src | head -n 80 ||true
rg -n --type=ts "build.*Empty|empty.*predicate|Array.*Empty|isEmpty.*predicate" packages/orm/src | head -n 80 ||true

Repository: zenstackhq/zenstack

Length of output: 7293


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Read isEmpty implementation
FILE="packages/orm/src/client/functions.ts"echo"== ${FILE}: around isEmpty =="
rg -n "export const isEmpty""$FILE"
LINE=$(rg -n "export const isEmpty""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYimport itertoolspath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}")PY# 2) Follow any dialect helper referenced by isEmptyecho"== code search: buildCollectionEmptyPredicate / empty predicate =="
rg -n "build.*Empty|Empty.*Predicate|isEmpty" packages/orm/src/client/crud/dialects packages/orm/src/client | head -n 80
# 3) Look for dialect method definitions that match what isEmpty calls# (we’ll search for the most likely helper names found above)

Repository: zenstackhq/zenstack

Length of output: 6628


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Base dialect isEmpty handling
FILE="packages/orm/src/client/crud/dialects/base-dialect.ts"echo"== ${FILE} around isEmpty switch =="
rg -n "case 'isEmpty'""$FILE"
LINE=$(rg -n "case 'isEmpty'""$FILE"| head -n1 | cut -d: -f1)
start=$((LINE-60)); end=$((LINE+120))
python3 - <<PYpath="$FILE"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPY# 2) PostgreSQL dialect array helpers
FILE2="packages/orm/src/client/crud/dialects/postgresql.ts"echo"== code search in postgresql.ts: buildArrayLength/buildArrayContains/buildArrayHas* =="
rg -n "buildArrayLength|buildArrayContains|buildArrayHasEvery|buildArrayHasSome|getSqlType\\(""$FILE2"# Print likely relevant sections around those methodsforpatin"buildArrayLength""buildArrayContains""buildArrayHasEvery""buildArrayHasSome""getSqlType";do
LINE=$(rg -n "$pat""$FILE2"| head -n1 | cut -d: -f1 || true)if [ -n"${LINE:-}" ];then
start=$((LINE-40)); end=$((LINE+120))echo"== ${FILE2} around ${pat} (lines ${start}-${end}) =="
python3 - <<PYpath="$FILE2"start=$start; end=$endwith open(path,'r',encoding='utf-8') as f: for i,line in enumerate(f, start=1): if start<=i<=end: print(f"{i}:{line.rstrip()}") if i>end: breakPYfidone# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgresecho"== e2e: search for hasEvery/hasSome/has with enum arrays =="
rg -n --type=ts "(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm/policy tests/e2e/orm/client-api | head -n 200
# specifically within Role[]/enum contextecho"== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||trueecho"== e2e: search for enum arrays 'Status.ACTIVE' has/hasEvery/hasSome =="
rg -n --type=ts "Status\\.ACTIVE.*(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm | head -n 200 ||true

Repository: zenstackhq/zenstack

Length of output: 50375


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find where Role[]/roles is used with list predicate operators
rg -n --type=ts "roles\\s*Role\\[\\]|roles:|where:.*roles|\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\b" tests/e2e/orm/policy/ -S
# Inspect collection-predicate test around the Role/roles definition and nearby assertions
FILE="tests/e2e/orm/policy/collection-predicate.test.ts"echo"== ${FILE} (snippet around Role definition) =="
sed -n '110,220p'"$FILE"echo"== ${FILE} (snippet around later Role definition/uses) =="
sed -n '220,380p'"$FILE"echo"== ${FILE} (snippet around more Role definitions/uses) =="
sed -n '380,560p'"$FILE"# Confirm provider usage in the policy tests (best-effort: look for createPolicyTestClient options)echo"== ${FILE}: createPolicyTestClient provider config =="
rg -n "createPolicyTestClient\\(|provider:\\s*'postgresql'|postgresql""$FILE"||true# Also search for other e2e files using "roles Role[]" with has/hasSome/hasEvery
rg -n --type=ts "roles\\s*Role\\[\\]" tests/e2e/orm -S
rg -n --type=ts "roles.*\\{.*(hasEvery|hasSome|has)\\b" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 15886


🏁 Script executed:

#!/bin/bashset -euo pipefail
# 1) Find places where enum arrays are used with has/hasSome/hasEvery
rg -n --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S > /tmp/has_ops.txt ||true# Reduce to lines that mention enum-like identifiers (heuristic)# Status.ACTIVE/Role/AUTHOR/EDITOR are common in these schemas
rg -n --type=ts "(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE).*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\b|AUTHOR|EDITOR|ACTIVE)" /tmp/has_ops.txt ||true# 2) Specifically look for enum value identifiers with has/hasSome/hasEvery
rg -n --type=ts "(Status\\.|Role\\.)\\s*.*\\b(hasEvery|hasSome|has)\\b|\\b(hasEvery|hasSome|has)\\b.*(Status\\.|Role\\.)" tests/e2e/orm -S ||true# 3) Look for array filters on a known enum-array field pattern: "<fieldName>: { has: ... }"
rg -n --type=ts "[a-zA-Z0-9_]+\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|has)\\b" tests/e2e/orm -S | head -n 200
# 4) Identify any schemas in e2e that define enums and also declare an enum array field.# e.g. "enum Status { ... }" and later "status Status[]"
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{|[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/enum_defs_and_arrays.txt ||true
head -n 200 /tmp/enum_defs_and_arrays.txt
# 5) If we found candidate files, inspect them quickly (only if present)# (We’ll pick the isEmpty-function-pg file plus any file that contains "enum" and "hasSome/hasEvery/has:".)
candidates="$(rg -l --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" tests/e2e/orm | tr '\n''')"forfin$(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S);doif rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{""$f";thenecho"== candidate with enum + has ops: $f =="
sed -n '1,220p'"$f"fidone

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Look for enum definitions that are likely used with array fields
rg -n --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/orm -S > /tmp/e2e_enum_defs.txt ||true# Extract candidate enum namesecho"== First 80 enum definitions =="
head -n 80 /tmp/e2e_enum_defs.txt
# Find enum array field declarations (e.g., status Status[])
rg -n --type=ts "\\b[A-Za-z0-9_]+\\s+[A-Za-z0-9_]+\\[\\]" tests/e2e/orm -S > /tmp/e2e_array_fields.txt ||trueecho"== First 80 array-typed field declarations =="
head -n 80 /tmp/e2e_array_fields.txt
# Specifically look for enum arrays that use has/hasSome/hasEvery
rg -n --type=ts "\\b(hasEvery|hasSome|\\bhas\\b)\\b" tests/e2e/orm -S | head -n 200
rg -n --type=ts "status\\s*\\{\\s*[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "status\\s*:\\s*\\{[^}]*\\b(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S ||true
rg -n --type=ts "\\benum\\s+Status\\b|\\bStatus\\s*\\[\\]" tests/e2e/orm -S ||true# Look for runtime tests (non typecheck) involving Status.ACTIVE in where filters
rg -n --type=ts "Status\\.ACTIVE" tests/e2e/orm -S ||true

Repository: zenstackhq/zenstack

Length of output: 24846


Confirm PG enum→text mapping is intentional, and add runtime coverage for enum-array membership operators

 if (isEnum(this.schema, zmodelType)) {
// reduce enum to text for type compatibility
return 'text';
  • packages/orm/src/client/crud/dialects/postgresql.ts intentionally maps enum types to 'text' (“reduce enum to text for type compatibility”).
  • packages/orm/src/client/crud/dialects/base-dialect.ts further casts enum arrays to text[] in buildArrayFilter for array comparisons.
  • tests/e2e/orm/policy/isempty-function-pg.test.ts covers enum Role + roles Role[] with provider: 'postgresql', but it only exercises isEmpty(roles) (array-length), not enum-array membership (has / hasSome / hasEvery).
  • Existing enum ... has/hasEvery references found in tests/e2e/orm/schemas/typing/typecheck.ts are typechecking-only, not runtime assertions.

Add a Postgres e2e test that exercises roles enum-array filters with has/hasSome/hasEvery to ensure the enum→text[] casting preserves semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 502 - 504,
Add an e2e test for Postgres that verifies enum-array membership operators work
when enums are mapped to text (so the cast to text[] in buildArrayFilter
preserves semantics): create a schema with enum Role and field roles Role[],
insert records with various role combinations, then run queries using the array
filters has, hasSome, and hasEvery against roles and assert expected matches for
provider: 'postgresql' (mirror the style of
tests/e2e/orm/policy/isempty-function-pg.test.ts); ensure the test targets
behavior in postgresql.ts (isEnum) and base-dialect.ts (buildArrayFilter) by
asserting correct runtime results for each operator.

@ymc9
ymc9 merged commit 431ad02 into zenstackhq:devJun 12, 2026
10 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sanny-io@ymc9