Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heavy-params-bind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': patch
---

Fix `formatQuery` (used by `live.query`/`live.incrementalQuery` to inline parameters) emitting `%NL` instead of the positional `%N$L` format specifier. A bare `%NL` is "min width N" and consumes `format()` arguments sequentially, so placeholders that are out of textual order silently bound the wrong values, and repeated placeholders failed with `too few arguments for format()`.
7 changes: 5 additions & 2 deletions packages/pglite/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ export async function formatQuery(

const dataTypeIDs = parseDescribeStatementResults(messages)

// replace $1, $2, etc with %1L, %2L, etc
// replace $1, $2, etc with %1$L, %2$L, etc
// The `$` in `%n$L` is required for positional arguments; a bare `%nL` is
// "min width n" and makes format() consume arguments sequentially, binding
// the wrong values when placeholders are out of order or repeated.
const subbedQuery = query.replace(/\$([0-9]+)/g, (_, num) => {
return '%' + num + 'L'
return '%' + num + '$L'
})

const ret = await tx.query<{
Expand Down
32 changes: 32 additions & 0 deletions packages/pglite/tests/format.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,38 @@ describe('format', () => {
expect(ret3).toBe("SELECT * FROM test3 WHERE value = 'test';")
})

it('params out of textual order', async () => {
await pg.exec(`
CREATE TABLE test5 (
id SERIAL PRIMARY KEY,
value VARCHAR
);
`)
const ret5 = await formatQuery(
pg,
'SELECT * FROM test5 WHERE value = $2 AND id = $1;',
[1, 'test'],
)
expect(ret5).toBe("SELECT * FROM test5 WHERE value = 'test' AND id = '1';")
})

it('repeated param', async () => {
await pg.exec(`
CREATE TABLE test6 (
id SERIAL PRIMARY KEY,
value VARCHAR
);
`)
const ret6 = await formatQuery(
pg,
'SELECT * FROM test6 WHERE value = $1 OR value = $1;',
['test'],
)
expect(ret6).toBe(
"SELECT * FROM test6 WHERE value = 'test' OR value = 'test';",
)
})

it('json', async () => {
await pg.exec(`
CREATE TABLE test4 (
Expand Down