Describe the bug
A {...} filter value is a Postgres array literal, not a PostgREST list, and it has its own quoting rules. in() and notIn() quote their values for the (...) list context via PostgrestReservedCharsRegexp, but nothing covers the {...} context. Seven builders join their elements with a bare comma:
contains, containedBy, overlaps, likeAllOf, likeAnyOf, ilikeAllOf, ilikeAnyOf.
An element that itself contains a comma is therefore split by Postgres into two elements. The query does not fail — it answers a different question.
To Reproduce
Table with two rows: id 1 holding ARRAY['a,b'] (one element), id 2 holding ARRAY['a','b'] (two elements).
await supabase.from('t').select('id').contains('tags', ['a,b'])
// URL: tags=cs.{a,b}
// expected: id 1
// actual: id 2
Measured on PostgREST 12 / Postgres 15:
| call |
URL produced |
actual result |
contains('tags', ['a,b']) |
tags=cs.{a,b} |
id 2 |
overlaps('tags', ['a,b']) |
tags=ov.{a,b} |
id 2 |
likeAllOf('name', ['*x,y*']) |
name=like(all).{*x,y*} |
[] |
contains('tags', ['a{b']) |
tags=cs.{a{b} |
error 22P02 malformed array literal |
The parsing rules, checked directly in Postgres 15:
'{a,b}'::text[] -> 2 elements: a | b
'{"a,b"}'::text[] -> 1 element: a,b
'{a{b}'::text[] -> ERROR: malformed array literal
'{NULL}'::text[] -> SQL NULL, not the text 'NULL'
'{ a }'::text[] -> 'a', surrounding spaces trimmed
Expected behavior
An element containing a comma, brace, quote, backslash, leading/trailing whitespace, or the empty string should be quoted so Postgres receives the element the caller passed. contains('tags', ['a,b']) should return id 1.
Why it matters
The first three rows above are the dangerous ones: no error is raised, and the caller gets rows that merely contain two different elements. A plausible wrong answer is harder to catch than a failure, and comma-bearing tags ("Last, First", "a,b") are ordinary user data.
System information
@supabase/postgrest-js on master
- PostgREST 12, Postgres 15
PR #2657 is open against this, with 12 assertions covering the seven builders and the cases that must not change (plain values, non-strings, the range and json branches of contains/containedBy/overlaps). 10 of the 12 fail on master.
Describe the bug
A
{...}filter value is a Postgres array literal, not a PostgREST list, and it has its own quoting rules.in()andnotIn()quote their values for the(...)list context viaPostgrestReservedCharsRegexp, but nothing covers the{...}context. Seven builders join their elements with a bare comma:contains,containedBy,overlaps,likeAllOf,likeAnyOf,ilikeAllOf,ilikeAnyOf.An element that itself contains a comma is therefore split by Postgres into two elements. The query does not fail — it answers a different question.
To Reproduce
Table with two rows: id 1 holding
ARRAY['a,b'](one element), id 2 holdingARRAY['a','b'](two elements).Measured on PostgREST 12 / Postgres 15:
contains('tags', ['a,b'])tags=cs.{a,b}overlaps('tags', ['a,b'])tags=ov.{a,b}likeAllOf('name', ['*x,y*'])name=like(all).{*x,y*}[]contains('tags', ['a{b'])tags=cs.{a{b}22P02malformed array literalThe parsing rules, checked directly in Postgres 15:
Expected behavior
An element containing a comma, brace, quote, backslash, leading/trailing whitespace, or the empty string should be quoted so Postgres receives the element the caller passed.
contains('tags', ['a,b'])should return id 1.Why it matters
The first three rows above are the dangerous ones: no error is raised, and the caller gets rows that merely contain two different elements. A plausible wrong answer is harder to catch than a failure, and comma-bearing tags (
"Last, First","a,b") are ordinary user data.System information
@supabase/postgrest-jsonmasterPR #2657 is open against this, with 12 assertions covering the seven builders and the cases that must not change (plain values, non-strings, the range and json branches of
contains/containedBy/overlaps). 10 of the 12 fail onmaster.