Fixes for binary protocol array handling (#3494)

* fix(pg-protocol): specify number of result column format codes

Fixes a bug when binary format. We must specify both:

- the number of result column format codes
- the result column format codes

The text format case was working by accident. When using text format, the
intention was to set the format code to 0. Instead, we set the number
of result column format codes was set to 0. This is valid because it indicates
that all result columns should use the default format (text).

When using binary format, the intention was to set the format code to 1.
Instead, we set the number of result column format codes to 1.
Importantly, we never set a result column format code. This caused an
error: 'insufficient data left in message'. 

We now always set the number of result column format codes to '1'. The
value of '1' has special meaning:

> or one, in which case the specified format code is applied to all result columns (if any)

We then set a single column format code based on whether the connection
(or query) is set to binary.


Fixes #3487

* fix(pg): use a Buffer when parsing binary

The call to parseArray was not working as expected because the value was
being sent as a string instead of a Buffer. The binary parsers in
pg-types all assume the incoming value is a Buffer.
This commit is contained in:
Herman J. Radtke III 2025-06-19 16:37:04 -04:00 committed by GitHub
parent cd877a5761
commit e00aac1398
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 33 additions and 1 deletions

View File

@ -96,6 +96,7 @@ describe('serializer', () => {
.addCString('')
.addInt16(0)
.addInt16(0)
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
@ -123,6 +124,7 @@ describe('serializer', () => {
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing'))
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
@ -149,6 +151,7 @@ describe('serializer', () => {
.addInt32(-1)
.addInt32(-1)
.addInt32(-1)
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
@ -176,6 +179,7 @@ describe('serializer', () => {
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing', 'utf-8'))
.addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)

View File

@ -157,6 +157,8 @@ const bind = (config: BindOpts = {}): Buffer => {
writer.addInt16(len)
writer.add(paramWriter.flush())
// all results use the same format code
writer.addInt16(1)
// format code
writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING)
return writer.flush(code.bind)

View File

@ -66,7 +66,8 @@ class Result {
const rawValue = rowData[i]
const field = this.fields[i].name
if (rawValue !== null) {
row[field] = this._parsers[i](rawValue)
const v = this.fields[i].format === 'binary' ? Buffer.from(rawValue) : rawValue
row[field] = this._parsers[i](v)
} else {
row[field] = null
}

View File

@ -0,0 +1,25 @@
const helper = require('../test-helper')
const assert = require('assert')
const suite = new helper.Suite()
suite.testAsync('allows you to switch between format modes for arrays', async () => {
const client = new helper.pg.Client()
await client.connect()
const r1 = await client.query({
text: 'SELECT CAST($1 AS INT[]) as a',
values: [[1, 2, 8]],
binary: false,
})
assert.deepEqual([1, 2, 8], r1.rows[0].a)
const r2 = await client.query({
text: 'SELECT CAST($1 AS INT[]) as a',
values: [[4, 5, 6]],
binary: true,
})
assert.deepEqual([4, 5, 6], r2.rows[0].a)
await client.end()
})