Skip to content

fix(postgrest): quote array elements with reserved chars in contains/containedBy/overlaps - #2486

Open
spokodev wants to merge 4 commits into
supabase:masterfrom
spokodev:w33/postgrest-array-literal-quoting
Open

spokodev wants to merge 4 commits into
supabase:masterfrom
spokodev:w33/postgrest-array-literal-quoting

Conversation

@spokodev

@spokodev spokodev commented Jul 2, 2026

Copy link
Copy Markdown

The array branches of .contains(), .containedBy() and .overlaps() build the
Postgres array literal with a raw value.join(','), so an array element that
contains a comma is split into several elements and the server evaluates a
different filter than the one requested.

For example .contains('tags', ['Doe, John', 'admin']) emitted
tags=cs.{Doe, John,admin}, which Postgres reads as three elements
(Doe, John, admin) instead of the intended two. The correct literal is
tags=cs.{"Doe, John",admin}. The same defect applied to containedBy and
overlaps.

A Postgres array literal requires elements that contain the delimiter, braces,
quotes, backslash or surrounding whitespace to be double quoted. The .in() and
.notIn() filters already quote reserved characters, and the realtime filter
builder does full quoting, so these three were the outliers.

The fix adds a small helper that double quotes and escapes only string elements
with reserved characters or surrounding whitespace. Numbers, booleans, plain
strings, and the range and JSON branches are unchanged. Added a test asserting
the emitted request URL.

@spokodev
spokodev requested review from a team as code owners July 2, 2026 13:17
@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2486

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2486

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2486

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2486

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2486

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2486

commit: 24a3d99

@mandarini mandarini 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.

Hi @spokodev, thank you so much for contributing to Supabase!

Good catch, this is a real bug. A couple of changes before merging: the regex still misses a couple of cases Postgres treats specially, an empty string element or the bare word null both get emitted unquoted and read back as SQL NULL instead of the intended value. Rather than expanding the regex, could you just always double quote string elements? That's what PostgREST's own pgBuildArrayLiteral does and it's simpler than what we have now.

Also, likeAllOf, likeAnyOf, ilikeAllOf, and ilikeAnyOf build array literals the same unquoted way, so the same fix should apply there too.

Thanks again for digging into this, looking forward to the update!

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: f13f4eef-483e-4fa0-ba7d-055ade6269a3

📥 Commits

Reviewing files that changed from the base of the PR and between 497952f and 21f4b72.

📒 Files selected for processing (2)
  • packages/core/postgrest-js/test/filters.test.ts
  • packages/core/postgrest-js/test/supabase/seed.sql

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Summary

Summary by CodeRabbit

  • Bug Fixes

    • Improved array-based filtering to correctly handle strings containing quotes, backslashes, commas, empty values, and the literal word “null.”
    • Fixed wildcard filtering so special characters are encoded correctly.
    • Preserved proper handling of numeric array values in filter requests.
  • Tests

    • Added coverage for array literal encoding across containment, overlap, and pattern-matching filters.

PostgrestFilterBuilder now serializes string elements in PostgREST array-literal filters with double quotes and escaping for backslashes and quotes. The shared helper is used by likeAllOf, likeAnyOf, ilikeAllOf, ilikeAnyOf, contains, containedBy, and overlaps. New tests verify encoding of special characters, empty strings, the literal "null", numeric arrays, and wildcard patterns.

Possibly related PRs

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 25d0e

Array-based PostgREST filters now preserve special string elements such as commas, quotes, empty strings, and literal null values rather than misparsing them. The affected filter variants have matching encoding and coverage, with no remaining merge-readiness risk identified.

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.

@spokodev

Copy link
Copy Markdown
Author

Thanks! Switched toPostgrestArrayLiteral to always double-quote string elements, mirroring pgBuildArrayLiteral — an empty string and the literal word null now round-trip instead of collapsing to SQL NULL. Applied the same helper to likeAllOf/likeAnyOf/ilikeAllOf/ilikeAnyOf, and added the empty-string/null cases to the tests.

@spokodev

spokodev commented Sep 7, 2026

Copy link
Copy Markdown
Author

Checking in on this one. The update you asked for went in on 30 July: toPostgrestArrayLiteral now always double-quotes string elements the way pgBuildArrayLiteral does, so an empty string and the literal word null round-trip instead of collapsing to SQL NULL, and the same helper is applied to likeAllOf, likeAnyOf, ilikeAllOf and ilikeAnyOf. Tests cover both cases.

Anything else you would like changed before this can go in?

@mandarini mandarini 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.

Hi @spokodev, sorry for taking so long to answer. Thanks for making the update.

One thing before this can merge: the new tests in array-literal.test.ts only check the constructed URL string, they don't run against a real PostgREST server. Every neighboring test for these same methods in test/filters.test.ts (e.g. contains with array, overlaps with array) is an integration test that hits the local Dockerized PostgREST instance and snapshots the actual response. Could you add a case there too, with a comma-containing array element, so we have proof the server parses the fixed literal correctly, not just that our own code produces the string we expect?

Also, can you please rebase with master just in case?

Thank you so much for sticking with this!

spokodev and others added 3 commits September 8, 2026 22:37
Mirror PostgREST's pgBuildArrayLiteral: quote every string element instead of
only those with reserved characters. An empty string and the literal word
`null` were emitted unquoted and read back as SQL NULL; quoting them preserves
the intended value. Apply the same helper to likeAllOf/likeAnyOf/ilikeAllOf/
ilikeAnyOf, which built their array literals the same unquoted way.
…tgREST

The array-literal tests only assert the constructed URL. Add an integration
case alongside the other contains/containedBy/overlaps tests so the server is
shown to read the quoted literal as one element, with a seed row whose element
contains a comma.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@spokodev
spokodev force-pushed the w33/postgrest-array-literal-quoting branch from 497952f to 21f4b72 Compare September 8, 2026 21:38
@spokodev

spokodev commented Sep 8, 2026

Copy link
Copy Markdown
Author

Done — rebased on master and added the integration case.

test/filters.test.ts now has contains with an array element containing a comma, sitting next to the existing contains with array and following the same shape, and test/supabase/seed.sql gains one cornercase row whose element carries a comma:

(4, ARRAY['Doe, John', 'admin']);

The three existing cornercase tests all filter on ['test'], and the basic.test.ts case asserts a duplicate key on id: 1, so the new row does not disturb any existing snapshot.

You were right that the URL assertion is only half the chain. I could not bring the Dockerized stack up locally (the daemon on this machine is wedged), so I verified the server half directly against PostgreSQL 14 with the same table and seed. The two literals are read differently:

literal parsed as elements
{Doe, John,admin} (before the fix) {Doe,John,admin} 3
{"Doe, John",admin} (after) {"Doe, John",admin} 2

and on the actual operator:

select id from cornercase where array_column @> '{"Doe, John"}';  -- 4
select id from cornercase where array_column @> '{Doe, John}';    -- 0 rows

So the unquoted form did not error, it silently matched a different set — which is what makes the integration case worth having. The client side of the same chain is already pinned in array-literal.test.ts (cs.{"Doe, John","admin"}), and this new test closes the loop through PostgREST in CI.

I can add the same case for containedBy and overlaps if you would rather have all three, though they share toPostgrestArrayLiteral, so one server-side case covers the literal construction.

@spokodev

Copy link
Copy Markdown
Author

@mandarini I merged master into the branch. The CI on the 8 September push never started: all four workflows ended with "This run likely failed because of a workflow file issue" and no jobs, as on another fork PR that day. So the integration test you asked for, contains with an array element containing a comma in test/filters.test.ts, has not run in CI yet; this push should run it once the workflows are approved.

This branch has not been deployed

No deployments
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.

2 participants