Skip to content

fix(postgrest): escape reserved characters in array filter values#1126

Open
AndroidPoet wants to merge 1 commit into
supabase:mainfrom
AndroidPoet:fix/postgrest-array-literal-escape-2
Open

fix(postgrest): escape reserved characters in array filter values#1126
AndroidPoet wants to merge 1 commit into
supabase:mainfrom
AndroidPoet:fix/postgrest-array-literal-escape-2

Conversation

@AndroidPoet

Copy link
Copy Markdown
Contributor

What

Array's PostgrestFilterValue conformance builds a PostgreSQL array literal by joining the elements' raw values with commas, with no escaping:

extension Array: PostgrestFilterValue where Element: PostgrestFilterValue {
  public var rawValue: String {
    "{\(map(\.rawValue).joined(separator: ","))}"
  }
}

So any element that contains a character with structural meaning inside an array literal is silently corrupted. For example, filtering an array/text[] column with ["a,b"] produces {a,b}, which PostgREST parses as the two values a and b instead of the single value a,b. The same happens for elements containing {, }, ", \, surrounding whitespace, an empty string, or the literal NULL.

This is the array-literal counterpart of the in() filter escaping fixed in #1061.

Fix

Quote and backslash-escape each element that needs it, reusing the same escaping approach as the existing in() filter helper (added alongside it in Helpers):

public var rawValue: String {
  let elements = map { element -> String in
    let raw = element.rawValue
    if raw.hasPrefix("{"), raw.hasSuffix("}") {
      return raw
    }
    return escapePostgRESTArrayLiteralElement(raw)
  }
  return "{\(elements.joined(separator: ","))}"
}

Elements that are already safe (admin, 9:00, numbers) are emitted verbatim, so existing valid usage is unchanged.

Note on nested arrays

An element whose raw value both starts with { and ends with } is treated as an already-formed nested array literal and passed through unquoted, so arrays of arrays ([[1, 2], [3, 4]] -> {{1,2},{3,4}}) keep working. The trade-off is that a String element that is literally {...} will not be quoted — this is inherent to the string-based rawValue design, which can't distinguish a nested [Int] (whose rawValue is {1,2}) from the string "{1,2}". Elements with partial braces (e.g. a{b) are still quoted.

Testing

Added tests covering reserved characters (comma, brace), quote/backslash escaping, whitespace/empty/NULL, nested array literals, the AnyJSON.array path, and that safe/numeric values stay unquoted. They fail on main and pass with this change.

  • swift test --filter PostgRESTTests — 105 passing (existing in() and snapshot tests unchanged)
  • ./scripts/format.sh — clean

Array's PostgrestFilterValue.rawValue joined elements with commas inside
{...} without escaping, so an element containing a comma, brace, quote,
backslash, surrounding whitespace, or the literal NULL corrupted the array
literal — e.g. ["a,b"] became {a,b}, which PostgREST reads as two separate
values. Quote and backslash-escape elements that need it, mirroring the
existing in() filter escaping. Nested array literals ({...}) pass through
unchanged so arrays of arrays keep working.
@AndroidPoet
AndroidPoet requested review from a team and grdsdev as code owners July 13, 2026 19:41
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