Skip to content

Commit a11064c

Browse files
committed
fix(pg-protocol): reset Writer state when error throws from valueMapper in bind()
When valueMapper throws during serialize.bind(), the module-level singleton Writer instances (writer and paramWriter) are left with partial data from the interrupted operation. This corrupts all subsequent serializer calls since they share the same Writer instances. Add Writer.clear() method that resets the write cursor without allocating a new buffer (zero overhead on the happy path). Wrap writeValues() in bind() with try-catch to clear both writers on error.
1 parent ecff60d commit a11064c

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

packages/pg-protocol/src/buffer-writer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,9 @@ export class Writer {
8282
this.buffer = Buffer.allocUnsafe(this.size)
8383
return result
8484
}
85+
86+
public clear(): void {
87+
this.offset = 5
88+
this.headerPosition = 0
89+
}
8590
}

packages/pg-protocol/src/outbound-serializer.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,62 @@ describe('serializer', () => {
273273
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true)
274274
assert.deepEqual(actual, expected)
275275
})
276+
277+
describe('bind error recovery', () => {
278+
const throwingMapper = () => {
279+
throw new Error('valueMapper error')
280+
}
281+
282+
it('produces correct bind output after a valueMapper exception', () => {
283+
assert.throws(() => {
284+
serialize.bind({
285+
values: ['fail'],
286+
valueMapper: throwingMapper,
287+
})
288+
}, /valueMapper error/)
289+
290+
const actual = serialize.bind({
291+
portal: 'bang',
292+
statement: 'woo',
293+
values: ['1', 'hi', null, 'zing'],
294+
})
295+
const expectedBuffer = new BufferList()
296+
.addCString('bang')
297+
.addCString('woo')
298+
.addInt16(4)
299+
.addInt16(0)
300+
.addInt16(0)
301+
.addInt16(0)
302+
.addInt16(0)
303+
.addInt16(4)
304+
.addInt32(1)
305+
.add(Buffer.from('1'))
306+
.addInt32(2)
307+
.add(Buffer.from('hi'))
308+
.addInt32(-1)
309+
.addInt32(4)
310+
.add(Buffer.from('zing'))
311+
.addInt16(1)
312+
.addInt16(0)
313+
.join(true, 'B')
314+
assert.deepEqual(actual, expectedBuffer)
315+
})
316+
317+
it('produces correct output from other serializer methods after a failed bind', () => {
318+
assert.throws(() => {
319+
serialize.bind({
320+
values: ['fail'],
321+
valueMapper: throwingMapper,
322+
})
323+
}, /valueMapper error/)
324+
325+
const parseActual = serialize.parse({ text: '!' })
326+
const parseExpected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
327+
assert.deepEqual(parseActual, parseExpected)
328+
329+
const queryActual = serialize.query('select 1')
330+
const queryExpected = new BufferList().addCString('select 1').join(true, 'Q')
331+
assert.deepEqual(queryActual, queryExpected)
332+
})
333+
})
276334
})

packages/pg-protocol/src/serializer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,13 @@ const bind = (config: BindOpts = {}): Buffer => {
152152
writer.addCString(portal).addCString(statement)
153153
writer.addInt16(len)
154154

155-
writeValues(values, config.valueMapper)
155+
try {
156+
writeValues(values, config.valueMapper)
157+
} catch (err) {
158+
writer.clear()
159+
paramWriter.clear()
160+
throw err
161+
}
156162

157163
writer.addInt16(len)
158164
writer.add(paramWriter.flush())

0 commit comments

Comments
 (0)