Skip to content

postgrest-js: contains/overlaps and the likeAllOf family do not quote array literal elements, so a comma inside a value silently returns the wrong rows #2669

Description

@PedroHenrique0713

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions