Skip to content

fix(orm): "array_contains" filter fixes#480

Merged
ymc9 merged 2 commits intodevfrom
fix/array_contains
Dec 12, 2025
Merged

fix(orm): "array_contains" filter fixes#480
ymc9 merged 2 commits intodevfrom
fix/array_contains

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Dec 12, 2025

Summary by CodeRabbit

  • Bug Fixes

    • PostgreSQL: JSON array containment now correctly accepts either a single value or an array by normalizing inputs.
    • SQLite: Array inputs for JSON array containment are now rejected with a clear not-supported response.
  • Tests

    • Added provider-specific test coverage to validate array filtering behavior across databases.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 12, 2025 14:45
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Postgres JSON array containment now preserves input arrays instead of always wrapping values; SQLite rejects array-valued inputs for array_contains and switches json_each→jsonb_each; tests add a PostgreSQL-specific assertion for array_contains with an array argument.

Changes

Cohort / File(s) Summary
Postgres JSON array normalization
packages/orm/src/client/crud/dialects/postgresql.ts
array_contains now treats an already-array value as an array (uses it directly) and only wraps non-array values into a single-element array when building JSONB containment checks.
SQLite JSON array behavior & imports
packages/orm/src/client/crud/dialects/sqlite.ts
Added import of createNotSupportedError; buildJsonArrayFilter now throws createNotSupportedError if the provided value is an array for array_contains; buildJsonArrayExistsPredicate now uses jsonb_each instead of json_each.
Provider-specific test addition
tests/e2e/orm/client-api/json-filter.test.ts
Test updated to run an additional PostgreSQL-only assertion for array_contains using an array argument ['react'], gated by a provider check.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to:
    • Correct detection of array vs scalar in PostgreSQL normalization (edge cases: null, nested arrays).
    • Placement and wording of createNotSupportedError in SQLite to avoid false negatives.
    • Semantic impact of switching json_eachjsonb_each.
    • Test gating to ensure it only affects PostgreSQL runs.

Possibly related PRs

Poem

🐰 I hop through JSON lanes with glee,
I keep arrays when they’re meant to be,
I warn when SQLite says "not for thee",
Tests cheer for Postgres' tidy spree —
A little hop, a tidy key. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(orm): "array_contains" filter fixes' directly addresses the main changes across all modified files, which involve correcting array_contains filter behavior in PostgreSQL and SQLite dialects.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/array_contains

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c060b2 and b827139.

📒 Files selected for processing (1)
  • packages/orm/src/client/crud/dialects/sqlite.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/orm/src/client/crud/dialects/sqlite.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-test (20.x, sqlite)
  • GitHub Check: claude-review
  • GitHub Check: build-test (20.x, postgresql)

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.

@claude
Copy link

claude bot commented Dec 12, 2025

Claude finished @ymc9's task —— View job


Code Review Complete ✅

I've analyzed the array_contains filter fixes across PostgreSQL/SQLite dialects. Here's my comprehensive review:

Todo List:

  • Read and analyze PostgreSQL dialect changes
  • Read and analyze SQLite dialect changes
  • Review test additions and coverage
  • Check for potential bugs and security issues
  • Assess performance considerations
  • Provide comprehensive feedback

Summary

This PR fixes the array_contains filter implementation to handle both single values and arrays, with appropriate database-specific behavior.

Code Quality & Best Practices ✅

PostgreSQL Changes (packages/orm/src/client/crud/dialects/postgresql.ts:471-474)

  • Good: Properly handles both array and single value inputs
  • Good: Maintains consistent pattern matching structure
  • Good: Uses clear variable naming (v for processed value)

SQLite Changes (packages/orm/src/client/crud/dialects/sqlite.ts:377-382, 399)

  • Good: Explicit error handling for unsupported array input
  • Good: Clear error message explaining SQLite limitation
  • Good: Fixed function name from json_each to jsonb_each for consistency
  • Minor: Import addition for createNotSupportedError is appropriate

Bug Fixes & Correctness ✅

  1. PostgreSQL: Fixed to handle array inputs properly instead of always wrapping in array
  2. SQLite: Added explicit validation to reject array inputs with helpful error
  3. SQLite: Fixed json_eachjsonb_each function name (critical bug fix)

Security Concerns ✅

  • No security issues identified
  • Proper input validation in SQLite dialect prevents invalid operations
  • SQL injection protection maintained through parameterized queries

Performance Considerations ✅

PostgreSQL:

  • Improved: Avoids double-wrapping arrays, which could affect query performance
  • The @> operator remains efficient for containment checks

SQLite:

  • The EXISTS subquery approach is appropriate for SQLite's capabilities
  • Function name fix ensures queries actually execute correctly

Test Coverage ✅

New Test (tests/e2e/orm/client-api/json-filter.test.ts:762-773):

  • Good: Tests PostgreSQL-specific array input behavior
  • Good: Properly scoped with provider check
  • Good: Tests both single value and array scenarios
  • Missing: No test for SQLite error case with array input

Recommendations

  1. Add SQLite Error Test: Consider adding a test to verify the SQLite error when passing arrays:
if ((db.$schema.provider.type as any) === 'sqlite') {
    await expect(
        db.foo.findFirst({
            where: {
                data: {
                    path: '$.tags',
                    array_contains: ['react'],
                },
            },
        })
    ).toBeRejected();
}
  1. Consider Documentation: The behavioral difference between PostgreSQL and SQLite should be documented in user-facing docs.

Overall Assessment ⭐⭐⭐⭐⭐

Excellent PR - Addresses real bugs with appropriate database-specific solutions. The PostgreSQL fix enables proper array containment checking, while the SQLite fix prevents runtime errors with clear messaging. The jsonb_each fix is critical for SQLite functionality.

Changes are minimal, focused, and well-tested. No breaking changes, proper error handling, and maintains backward compatibility for existing single-value usage.


Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/orm/src/client/crud/dialects/sqlite.ts (1)

399-399: Duplicate issue: Use json_each instead of jsonb_each.

Same issue as flagged in lines 377-383. SQLite does not support jsonb_each.

Apply this diff:

-                .selectFrom(this.eb.fn('jsonb_each', [receiver]).as('$items'))
+                .selectFrom(this.eb.fn('json_each', [receiver]).as('$items'))
🧹 Nitpick comments (1)
tests/e2e/orm/client-api/json-filter.test.ts (1)

762-774: Avoid as any + align provider detection with the rest of the test file.

This PG-only assertion is great coverage, but if ((db.$schema.provider.type as any) === 'postgresql') is brittle and inconsistent with the existing getTestDbProvider() usage (Line 534). Consider:

-        if ((db.$schema.provider.type as any) === 'postgresql') {
+        if (getTestDbProvider() === 'postgresql') {
             await expect(
                 db.foo.findFirst({
                     where: {
                         data: {
                             path: '$.tags',
                             array_contains: ['react'],
                         },
                     },
                 }),
             ).resolves.toMatchObject({ data: { tags: ['typescript', 'react', 'node'] } });
         }

Based on learnings, tests/e2e/**/* should stay harness-driven and portable across providers.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 293cf0d and 0c060b2.

📒 Files selected for processing (3)
  • packages/orm/src/client/crud/dialects/postgresql.ts (1 hunks)
  • packages/orm/src/client/crud/dialects/sqlite.ts (3 hunks)
  • tests/e2e/orm/client-api/json-filter.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/client-api/json-filter.test.ts
🧠 Learnings (3)
📚 Learning: 2025-10-21T16:04:56.292Z
Learnt from: ymc9
Repo: zenstackhq/zenstack-v3 PR: 319
File: packages/runtime/src/client/crud/dialects/base-dialect.ts:745-747
Timestamp: 2025-10-21T16:04:56.292Z
Learning: In packages/runtime/src/client/crud/dialects/base-dialect.ts, it's intentional that buildCursorFilter applies default ordering (via makeDefaultOrderBy fallback) while buildOrderBy does not. This ensures cursor-based pagination always has stable ordering for correctness, while regular queries remain unordered unless explicitly specified. This design is to be consistent with Prisma's pagination requirements.

Applied to files:

  • packages/orm/src/client/crud/dialects/postgresql.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/client-api/json-filter.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/client-api/json-filter.test.ts
🧬 Code graph analysis (2)
packages/orm/src/client/crud/dialects/postgresql.ts (1)
packages/orm/src/helpers.ts (1)
  • sql (1-1)
packages/orm/src/client/crud/dialects/sqlite.ts (1)
packages/orm/src/client/errors.ts (1)
  • createNotSupportedError (143-145)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: build-test (20.x, postgresql)
  • GitHub Check: build-test (20.x, sqlite)
🔇 Additional comments (3)
packages/orm/src/client/crud/dialects/postgresql.ts (1)

471-474: LGTM! Normalization logic correctly implements JSONB containment semantics.

The array normalization ensures the PostgreSQL @> operator receives a properly formatted JSONB array for containment checks. When value is already an array, it's used directly; when it's a scalar, wrapping it in an array enables the containment check to work correctly.

packages/orm/src/client/crud/dialects/sqlite.ts (2)

17-17: LGTM!

The added imports support the new error handling for unsupported array operations in SQLite.


378-380: No action needed—behavior is intentional and properly tested.

The array rejection for array_contains in SQLite is a deliberate design difference from PostgreSQL. The test suite already accounts for this: the test at line 768 of json-filter.test.ts that uses array_contains: ['react'] is wrapped in a conditional (if ((db.$schema.provider.type as any) === 'postgresql')) and only runs for PostgreSQL, which natively supports array containment checks. SQLite's implementation restricts the input to single values and provides a clear error message when arrays are passed, which is the expected behavior for this database.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes issues with the array_contains filter operation for JSON fields, addressing provider-specific behavior differences between PostgreSQL and SQLite. The main fix involves allowing PostgreSQL to accept both single values and arrays (leveraging the @> containment operator), while SQLite explicitly rejects arrays since it can only check for single values using jsonb_each. Additionally, a bug in SQLite's buildArrayItemFilter was corrected to use jsonb_each instead of json_each.

Key changes:

  • PostgreSQL's array_contains now conditionally wraps single values in an array, allowing both single values and arrays as input
  • SQLite's array_contains now throws a NOT_SUPPORTED error when an array is passed, with added validation
  • Fixed SQLite's buildArrayItemFilter to use jsonb_each instead of json_each for consistency

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/orm/src/client/crud/dialects/postgresql.ts Modified array_contains to conditionally wrap values, supporting both single values and arrays
packages/orm/src/client/crud/dialects/sqlite.ts Added array validation to reject arrays in array_contains, fixed json_each to jsonb_each in buildArrayItemFilter, imported createNotSupportedError
tests/e2e/orm/client-api/json-filter.test.ts Added PostgreSQL-specific test for array_contains with array value ['react']

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@claude
Copy link

claude bot commented Dec 12, 2025

Claude finished @ymc9's task —— View job


Code Review Complete ✅

I've analyzed the array_contains filter fixes across PostgreSQL/SQLite dialects. Here's my comprehensive review:

Todo List:

  • Read and analyze PostgreSQL dialect changes
  • Read and analyze SQLite dialect changes
  • Review test additions and coverage
  • Check for potential bugs and security issues
  • Assess performance considerations
  • Provide comprehensive feedback

Summary

This PR fixes the array_contains filter implementation to handle both single values and arrays, with appropriate database-specific behavior.

Code Quality & Best Practices ✅

PostgreSQL Changes (packages/orm/src/client/crud/dialects/postgresql.ts:471-474)

  • Good: Properly handles both array and single value inputs
  • Good: Maintains consistent pattern matching structure
  • Good: Uses clear variable naming (v for processed value)

SQLite Changes (packages/orm/src/client/crud/dialects/sqlite.ts:377-382, 399)

  • Good: Explicit error handling for unsupported array input
  • Good: Clear error message explaining SQLite limitation
  • Good: Fixed function name from json_each to jsonb_each for consistency
  • Minor: Import addition for createNotSupportedError is appropriate

Bug Fixes & Correctness ✅

  1. PostgreSQL: Fixed to handle array inputs properly instead of always wrapping in array
  2. SQLite: Added explicit validation to reject array inputs with helpful error
  3. SQLite: Fixed json_eachjsonb_each function name (critical bug fix)

Security Concerns ✅

  • No security issues identified
  • Proper input validation in SQLite dialect prevents invalid operations
  • SQL injection protection maintained through parameterized queries

Performance Considerations ✅

PostgreSQL:

  • Improved: Avoids double-wrapping arrays, which could affect query performance
  • The @> operator remains efficient for containment checks

SQLite:

  • The EXISTS subquery approach is appropriate for SQLite's capabilities
  • Function name fix ensures queries actually execute correctly

Test Coverage ✅

New Test (tests/e2e/orm/client-api/json-filter.test.ts:762-773):

  • Good: Tests PostgreSQL-specific array input behavior
  • Good: Properly scoped with provider check
  • Good: Tests both single value and array scenarios
  • Missing: No test for SQLite error case with array input

Recommendations

  1. Add SQLite Error Test: Consider adding a test to verify the SQLite error when passing arrays:
if ((db.$schema.provider.type as any) === 'sqlite') {
    await expect(
        db.foo.findFirst({
            where: {
                data: {
                    path: '$.tags',
                    array_contains: ['react'],
                },
            },
        })
    ).toBeRejected();
}
  1. Consider Documentation: The behavioral difference between PostgreSQL and SQLite should be documented in user-facing docs.

Overall Assessment ⭐⭐⭐⭐⭐

Excellent PR - Addresses real bugs with appropriate database-specific solutions. The PostgreSQL fix enables proper array containment checking, while the SQLite fix prevents runtime errors with clear messaging. The jsonb_each fix is critical for SQLite functionality.

Changes are minimal, focused, and well-tested. No breaking changes, proper error handling, and maintains backward compatibility for existing single-value usage.


@ymc9 ymc9 merged commit cce7555 into dev Dec 12, 2025
5 checks passed
@ymc9 ymc9 deleted the fix/array_contains branch December 12, 2025 15:02
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