Skip to content

fix: isEmpty function when using postgresql dialect#2703

Open
sanny-io wants to merge 7 commits into
zenstackhq:devfrom
sanny-io:fix/postgres-dialect
Open

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

Conversation

@sanny-io

@sanny-io sanny-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

coderabbitai Bot 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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The 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 ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check 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

sanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Context: isEmpty straight up did not work on postgres. array_length requires 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

@coderabbitai coderabbitai Bot 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-io sanny-io changed the title fix: isEmpty function and in expressions with enums using postgresql dialect fix: isEmpty function when using postgresql dialect Jun 9, 2026
@sanny-io

sanny-io commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

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

@coderabbitai coderabbitai Bot 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 () => {
    const schema = `
model User {
    id        Int      `@id` `@default`(autoincrement())
    roles     Role[]

    @@allow('all', true)
    @@deny('create', isEmpty(roles))
}

enum Role {
    AUTHOR
    EDITOR
}
    `;

    const client = await createPolicyTestClient(schema, {
        usePrismaPush: true,
        provider: 'postgresql',
    });

    await expect(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

@coderabbitai coderabbitai Bot 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 () => {
    const schema = `
model User {
    id        Int      `@id` `@default`(autoincrement())
    roles     Role[]

    @@allow('all', true)
    @@deny('create', isEmpty(roles))
}

enum Role {
    AUTHOR
    EDITOR
}
    `;

    const client = await createPolicyTestClient(schema, {
        usePrismaPush: true,
        provider: 'postgresql',
    });

    await expect(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/bash
set -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,sys
path="packages/orm/src/client/crud/dialects/postgresql.ts"
start=470; end=530
with 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:
            break
PY

# 2) Find actual PG enum schema definitions/usages in e2e tests
echo "== e2e: schema enum definitions =="
rg -n --hidden --no-ignore -S --type=ts "enum\\s+[A-Za-z0-9_]+" tests/e2e/ || true

echo "== 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" || true

echo "== 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 dialect
echo "== 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/bash
set -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 e2e
echo "== 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/bash
set -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 || true

echo "== 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/bash
set -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 - <<PY
import itertools
path="$FILE"
start=$start; end=$end
with 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 isEmpty
echo "== 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/bash
set -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 - <<PY
path="$FILE"
start=$start; end=$end
with 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: break
PY

# 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 methods
for pat in "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 - <<PY
path="$FILE2"
start=$start; end=$end
with 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: break
PY
  fi
done

# 3) E2E: find has/hasEvery/hasSome involving enum arrays with Postgres
echo "== 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 context
echo "== e2e: search for Role[] + has/hasEvery/hasSome =="
rg -n --type=ts "Role\\[\\].{0,200}(hasEvery|hasSome|\\bhas\\b)" tests/e2e/orm -S || true

echo "== 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/bash
set -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/bash
set -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' ' ')"
for f in $(rg -l --type=ts "\\bhasEvery\\b|\\bhasSome\\b|\\bhas\\s*:" tests/e2e/orm -S); do
  if rg -q --type=ts "enum\\s+[A-Za-z0-9_]+\\s*\\{" "$f"; then
    echo "== candidate with enum + has ops: $f =="
    sed -n '1,220p' "$f"
  fi
done

Repository: zenstackhq/zenstack

Length of output: 39300


🏁 Script executed:

#!/bin/bash
set -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 names
echo "== 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 || true
echo "== 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.

Sign up for free to 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.

1 participant