Skip to content

Commit bf640ad

Browse files
committed
fix: quote column names in json_to_recordset AS clause
Column names in the json_to_recordset AS clause were unquoted, causing PostgreSQL to lowercase them. This meant camelCase JSON keys didn't match the lowercased column names, producing NULLs. The insert and csv/COPY methods already properly double-quote column names; this aligns the json method with them.
1 parent ff2802b commit bf640ad

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

packages/pglite-sync/src/apply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export async function applyMessagesToTableWithJson({
271271
SELECT x.* from json_to_recordset($1) as x(${columns
272272
.map(
273273
(x) =>
274-
`${x.column_name} ${x.udt_name.replace(/^_/, '')}` +
274+
`"${x.column_name}" ${x.udt_name.replace(/^_/, '')}` +
275275
(x.data_type === 'ARRAY' ? `[]` : ''),
276276
)
277277
.join(', ')})

packages/pglite-sync/test/sync.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,4 +1670,97 @@ describe('pglite-sync', () => {
16701670

16711671
shape.unsubscribe()
16721672
})
1673+
1674+
it('handles camelCase column names with json_to_recordset', async () => {
1675+
await pg.exec(`
1676+
CREATE TABLE IF NOT EXISTS camel_test (
1677+
id SERIAL PRIMARY KEY,
1678+
"firstName" TEXT,
1679+
"lastName" TEXT
1680+
);
1681+
`)
1682+
await pg.exec(`TRUNCATE camel_test;`)
1683+
1684+
let feedMessages: (messages: MultiShapeMessage[]) => Promise<void> = async (
1685+
_,
1686+
) => {}
1687+
MockMultiShapeStream.mockImplementation(() => ({
1688+
subscribe: vi.fn(
1689+
(cb: (messages: MultiShapeMessage[]) => Promise<void>) => {
1690+
feedMessages = (messages) =>
1691+
cb([
1692+
...messages,
1693+
{
1694+
shape: 'shape',
1695+
headers: {
1696+
control: 'up-to-date',
1697+
global_last_seen_lsn: '0',
1698+
},
1699+
},
1700+
])
1701+
},
1702+
),
1703+
unsubscribeAll: vi.fn(),
1704+
isUpToDate: true,
1705+
shapes: {
1706+
shape: {
1707+
subscribe: vi.fn(),
1708+
unsubscribeAll: vi.fn(),
1709+
},
1710+
},
1711+
}))
1712+
1713+
const shape = await pg.electric.syncShapeToTable({
1714+
shape: {
1715+
url: 'http://localhost:3000/v1/shape',
1716+
params: { table: 'camel_test' },
1717+
},
1718+
table: 'camel_test',
1719+
primaryKey: ['id'],
1720+
initialInsertMethod: 'json',
1721+
shapeKey: null,
1722+
})
1723+
1724+
const messages: MultiShapeMessage[] = [
1725+
{
1726+
headers: { operation: 'insert' as const },
1727+
key: 'id1',
1728+
value: {
1729+
id: 1,
1730+
firstName: 'Alice',
1731+
lastName: 'Smith',
1732+
},
1733+
shape: 'shape',
1734+
},
1735+
{
1736+
headers: { operation: 'insert' as const },
1737+
key: 'id2',
1738+
value: {
1739+
id: 2,
1740+
firstName: 'Bob',
1741+
lastName: 'Jones',
1742+
},
1743+
shape: 'shape',
1744+
},
1745+
]
1746+
1747+
await feedMessages(messages)
1748+
1749+
await vi.waitUntil(async () => {
1750+
const result = await pg.sql<{ count: number }>`
1751+
SELECT COUNT(*) as count FROM camel_test;
1752+
`
1753+
return result.rows[0].count === 2
1754+
})
1755+
1756+
const result = await pg.sql`
1757+
SELECT * FROM camel_test ORDER BY id;
1758+
`
1759+
expect(result.rows).toEqual([
1760+
{ id: 1, firstName: 'Alice', lastName: 'Smith' },
1761+
{ id: 2, firstName: 'Bob', lastName: 'Jones' },
1762+
])
1763+
1764+
shape.unsubscribe()
1765+
})
16731766
})

0 commit comments

Comments
 (0)