From b03b1992a3004fdbe4223ce3d97c1eddc7843e6f Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 21:32:28 +0200 Subject: [PATCH 1/4] fixes #997 --- packages/pglite/src/types.ts | 17 ++++++++++++++--- packages/pglite/tests/basic.test.ts | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/pglite/src/types.ts b/packages/pglite/src/types.ts index dec8a9388..8f02fb110 100644 --- a/packages/pglite/src/types.ts +++ b/packages/pglite/src/types.ts @@ -310,13 +310,24 @@ function arrayParserLoop( s.last = ++s.i xs.push(arrayParserLoop(s, x, parser, typarray)) } else if (s.char === '}') { + if (s.last < s.i) { + const el = x.slice(s.last, s.i) + if (el === 'NULL' && !s.quoted) { + xs.push(null) + } else { + xs.push(parser ? parser(el) : el) + } + } s.quoted = false - s.last < s.i && - xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i)) s.last = s.i + 1 break } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') { - xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i)) + const el = x.slice(s.last, s.i) + if (el === 'NULL' && !s.quoted) { + xs.push(null) + } else { + xs.push(parser ? parser(el) : el) + } s.last = s.i + 1 } s.p = s.char diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index b845b4f1f..d44e7c260 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -715,5 +715,23 @@ await testEsmCjsAndDTC(async (importType) => { expect(process.exitCode).toEqual(origExitCode) }) + + it("text[] with NULL elements returns null, not string 'NULL'", async () => { + const pg = await PGlite.create(); + + await pg.exec("CREATE TEMP TABLE t (str_val text, arr_val text[])"); + + await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, [null, "hello", 'NULL']]); + await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, ['NULL', null, 'NULL']]); + await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, ['NULL', "hello", null]]); + await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, [null, null, null]]); + + const res = await pg.query("SELECT str_val, arr_val FROM t"); + expect(res.rows[0].str_val).toEqual(null); + expect(res.rows[0].arr_val).toEqual([null, "hello", 'NULL']); + expect(res.rows[1].arr_val).toEqual(['NULL', null, 'NULL']); + expect(res.rows[2].arr_val).toEqual(['NULL', "hello", null]); + expect(res.rows[3].arr_val).toEqual([null, null, null]); + }) }) }) From 0890f5569917e4c30a2e572f7cd5f1176fce841c Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 21:33:24 +0200 Subject: [PATCH 2/4] style --- packages/pglite/tests/basic.test.ts | 36 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index d44e7c260..b5b0b777e 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -717,21 +717,33 @@ await testEsmCjsAndDTC(async (importType) => { }) it("text[] with NULL elements returns null, not string 'NULL'", async () => { - const pg = await PGlite.create(); + const pg = await PGlite.create() - await pg.exec("CREATE TEMP TABLE t (str_val text, arr_val text[])"); + await pg.exec('CREATE TEMP TABLE t (str_val text, arr_val text[])') - await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, [null, "hello", 'NULL']]); - await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, ['NULL', null, 'NULL']]); - await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, ['NULL', "hello", null]]); - await pg.query("INSERT INTO t (str_val, arr_val) VALUES ($1, $2)", [null, [null, null, null]]); + await pg.query('INSERT INTO t (str_val, arr_val) VALUES ($1, $2)', [ + null, + [null, 'hello', 'NULL'], + ]) + await pg.query('INSERT INTO t (str_val, arr_val) VALUES ($1, $2)', [ + null, + ['NULL', null, 'NULL'], + ]) + await pg.query('INSERT INTO t (str_val, arr_val) VALUES ($1, $2)', [ + null, + ['NULL', 'hello', null], + ]) + await pg.query('INSERT INTO t (str_val, arr_val) VALUES ($1, $2)', [ + null, + [null, null, null], + ]) - const res = await pg.query("SELECT str_val, arr_val FROM t"); - expect(res.rows[0].str_val).toEqual(null); - expect(res.rows[0].arr_val).toEqual([null, "hello", 'NULL']); - expect(res.rows[1].arr_val).toEqual(['NULL', null, 'NULL']); - expect(res.rows[2].arr_val).toEqual(['NULL', "hello", null]); - expect(res.rows[3].arr_val).toEqual([null, null, null]); + const res = await pg.query('SELECT str_val, arr_val FROM t') + expect(res.rows[0].str_val).toEqual(null) + expect(res.rows[0].arr_val).toEqual([null, 'hello', 'NULL']) + expect(res.rows[1].arr_val).toEqual(['NULL', null, 'NULL']) + expect(res.rows[2].arr_val).toEqual(['NULL', 'hello', null]) + expect(res.rows[3].arr_val).toEqual([null, null, null]) }) }) }) From e8ef08d08738ddd65b4eafaa447d2af4be237f3d Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Fri, 22 May 2026 22:05:29 +0200 Subject: [PATCH 3/4] changeset --- .changeset/calm-parrots-shave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/calm-parrots-shave.md diff --git a/.changeset/calm-parrots-shave.md b/.changeset/calm-parrots-shave.md new file mode 100644 index 000000000..6dbf33778 --- /dev/null +++ b/.changeset/calm-parrots-shave.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/pglite': patch +--- + +Allow parsing of nulls in arrays #997 From c5a12fcd558b64b84346843b63b0093afacc2d77 Mon Sep 17 00:00:00 2001 From: Tudor Zaharia Date: Sat, 23 May 2026 09:00:26 +0200 Subject: [PATCH 4/4] style --- packages/pglite/tests/basic.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index b5b0b777e..3c28d5382 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -716,7 +716,7 @@ await testEsmCjsAndDTC(async (importType) => { expect(process.exitCode).toEqual(origExitCode) }) - it("text[] with NULL elements returns null, not string 'NULL'", async () => { + it("arrays with NULL elements should return null, not string 'NULL'", async () => { const pg = await PGlite.create() await pg.exec('CREATE TEMP TABLE t (str_val text, arr_val text[])') @@ -744,6 +744,11 @@ await testEsmCjsAndDTC(async (importType) => { expect(res.rows[1].arr_val).toEqual(['NULL', null, 'NULL']) expect(res.rows[2].arr_val).toEqual(['NULL', 'hello', null]) expect(res.rows[3].arr_val).toEqual([null, null, null]) + + await pg.exec('CREATE TEMP TABLE v (arr_int int[])') + await pg.query('INSERT INTO v (arr_int) VALUES ($1)', [[null, 123, 0]]) + const resInt = await pg.query('SELECT arr_int FROM v') + expect(resInt.rows[0].arr_int).toEqual([null, 123, 0]) }) }) })