Skip to content

fix(realtime): parse Postgres array literals instead of guessing - #2586

Open
Sy-D wants to merge 1 commit into
supabase:masterfrom
Sy-D:fix/realtime-array-literal
Open

fix(realtime): parse Postgres array literals instead of guessing#2586
Sy-D wants to merge 1 commit into
supabase:masterfrom
Sy-D:fix/realtime-array-literal

Conversation

@Sy-D

@Sy-D Sy-D commented Jul 31, 2026

Copy link
Copy Markdown

Description

toArray turned a Postgres array literal into a JS array by trying JSON.parse on it and, when that threw, splitting on commas. Neither is the array literal grammar, and the file already said so — a TODO above the first attempt and a WARNING above the fallback.

Postgres quotes an element whenever it is empty, spells NULL, or contains a delimiter, brace, quote, backslash or whitespace. A single literal therefore routinely mixes quoted and unquoted elements — and that is exactly where both strategies fail: JSON.parse rejects the unquoted ones, then the comma split tears the quoted ones apart mid-element.

column value literal Postgres sends toArray on master
['a,b', 'c'] {"a,b",c} ['"a', 'b"', 'c']
['hello world', 'plain'] {"hello world",plain} ['"hello world"', 'plain']
['x"y', 'z'] {"x\"y",z} ['"x\"y"', 'z']
['a', null, 'b'] {a,NULL,b} ['a', 'NULL', 'b']

I generated the middle column from a real Postgres 16 rather than reasoning about it, so these are the strings the server actually puts on the wire.

This is on the live postgres_changes path — RealtimeChannelconvertChangeDataconvertCelltoArray — so any subscription to a table with a text[] column whose values contain a space or a comma receives corrupted data, silently. NULL elements arrive as the string 'NULL'.

The existing tests pass because every case they cover happens to be one where one of the two strategies works: {a,b,c} and {1,2,3,4} are entirely unquoted, and the daterange case is entirely quoted.

What changed

parseArrayElements scans the literal per the documented grammar: quoted elements honour \\ and \", an unquoted NULL becomes the null element, and a quoted "NULL" stays the four-character string. null elements skip convertCell rather than being fed to toNumber/toBoolean.

Multidimensional arrays remain unsupported. {{1,2},{3,4}} was already mangled before this change (JSON.parse fails, the split produces ['{1', '2}', '{3', '4}']) and still is. Handling them properly needs a decision about what the element type means one level down, which felt out of scope for a fix; happy to follow up if you want it.

Testing

New packages/core/realtime-js/test/array-literal.test.ts — 9 tests covering comma, whitespace, embedded quote, embedded backslash, brace, empty string, both orderings of quoted/unquoted, unquoted NULL and quoted "NULL".

8 of the 9 fail on master. The one that passes there is {"NULL"}, which JSON.parse happens to get right.

Full realtime-js suite:

Test files Tests
master 26 passed 447 passed, 1 skipped
this branch 27 passed 456 passed, 1 skipped

Both fully green; the delta is exactly this file and its 9 tests. No existing test changed status.

nx lint realtime-js reports 536 errors both here and on master — all pre-existing. nx format:check and nx build realtime-js pass.

Type of Change

  • Bug fix (fix)

Checklist

  • Code formatted (nx format)
  • Unit tests added and passing
  • Package suite passing (vitest run, fully green)
  • Builds passing (nx build realtime-js)
  • Used conventional commits

`toArray` tried `JSON.parse` on the literal and fell back to splitting on
commas. Neither is the array literal grammar, and the file said as much:
a `TODO` on the first and a `WARNING` on the second.

Postgres quotes an element whenever it is empty, spells `NULL`, or
contains a delimiter, brace, quote, backslash or whitespace, so one
literal routinely mixes quoted and unquoted elements. `JSON.parse` then
fails on the unquoted ones and the comma split tears the quoted ones
apart mid-element:

    {"a,b",c}               -> ['"a', 'b"', 'c']
    {"hello world",plain}   -> ['"hello world"', 'plain']
    {a,NULL,b}              -> ['a', 'NULL', 'b']

Replace both with a scanner over the documented grammar: quoted elements
honour `\\` and `\"`, an unquoted `NULL` becomes the null element, and a
quoted `"NULL"` stays the four-character string.

Multidimensional arrays are still unsupported — they were before this
change too, and handling them needs a decision about the element type
that is out of scope here.
@Sy-D
Sy-D requested review from a team as code owners July 31, 2026 17:27
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0076df6f-fed3-4d22-96ce-34d1018616f9

📥 Commits

Reviewing files that changed from the base of the PR and between c9bf11d and 1c201a7.

📒 Files selected for processing (2)
  • packages/core/realtime-js/src/lib/transformers.ts
  • packages/core/realtime-js/test/array-literal.test.ts

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of PostgreSQL array values, including quoted elements, escaped characters, delimiters, whitespace, empty arrays, and NULL values.
    • Correctly distinguishes between null elements and the literal text "NULL".
  • Tests

    • Added coverage for complex array formats and edge cases.

Walkthrough

toArray now parses PostgreSQL array literals with a dedicated element parser. The parser handles quoting, escaping, delimiters, whitespace, empty values, and case-insensitive unquoted NULL. Quoted "NULL" remains a string. Non-null elements are converted with the scalar PostgreSQL type, while null elements remain null. Vitest tests cover mixed values, special-character escapes, empty strings, and null handling.

Sequence Diagram(s)

sequenceDiagram
  participant toArray
  participant parseArrayElements
  participant ScalarTypeParser
  toArray->>parseArrayElements: Parse PostgreSQL array literal
  parseArrayElements-->>toArray: Return parsed elements
  toArray->>ScalarTypeParser: Convert non-null elements
  ScalarTypeParser-->>toArray: Return converted array
Loading

Possibly related PRs


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.

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