From 0c23cccfc30ca38a6fb505ea2db363e44767ad05 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Thu, 16 Jul 2020 13:12:26 -0500 Subject: [PATCH 1/6] Faster bind function --- packages/pg/bench.js | 59 +++++++++++++++++++++------------------- packages/pg/lib/query.js | 28 +++++++++---------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/pg/bench.js b/packages/pg/bench.js index a668aa85f..5cb42ac78 100644 --- a/packages/pg/bench.js +++ b/packages/pg/bench.js @@ -45,37 +45,40 @@ const run = async () => { console.log('warmup done') const seconds = 5 - let queries = await bench(client, params, seconds * 1000) - console.log('') - console.log('little queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 733 qps') + for (let i = 0; i < 4; i++) { + let queries = await bench(client, params, seconds * 1000) + console.log('') + console.log('little queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 733 qps') - console.log('') - queries = await bench(client, seq, seconds * 1000) - console.log('sequence queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 1309 qps') + console.log('') + queries = await bench(client, seq, seconds * 1000) + console.log('sequence queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 1309 qps') - console.log('') - queries = await bench(client, insert, seconds * 1000) - console.log('insert queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 6303 qps') + console.log('') + queries = await bench(client, insert, seconds * 1000) + console.log('insert queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 6445 qps') - console.log('') - console.log('Warming up bytea test') - await client.query({ - text: 'INSERT INTO buf(name, data) VALUES ($1, $2)', - values: ['test', Buffer.allocUnsafe(104857600)], - }) - console.log('bytea warmup done') - const start = Date.now() - const results = await client.query('SELECT * FROM buf') - const time = Date.now() - start - console.log('bytea time:', time, 'ms') - console.log('bytea length:', results.rows[0].data.byteLength, 'bytes') - console.log('on my laptop best so far seen 1107ms and 104857600 bytes') + console.log('') + console.log('Warming up bytea test') + await client.query({ + text: 'INSERT INTO buf(name, data) VALUES ($1, $2)', + values: ['test', Buffer.allocUnsafe(104857600)], + }) + console.log('bytea warmup done') + const start = Date.now() + const results = await client.query('SELECT * FROM buf') + const time = Date.now() - start + console.log('bytea time:', time, 'ms') + console.log('bytea length:', results.rows[0].data.byteLength, 'bytes') + console.log('on my laptop best so far seen 1107ms and 104857600 bytes') + await new Promise((resolve) => setTimeout(resolve, 250)) + } await client.end() await client.end() diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 9cd0dab10..a1b061860 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -191,22 +191,22 @@ class Query extends EventEmitter { }) } - if (this.values) { - try { - this.values = this.values.map(utils.prepareValue) - } catch (err) { - this.handleError(err, connection) - return - } + // because we're mapping user supplied values to + // postgres wire protocol compatible values it could + // throw an exception, so try/catch this section + try { + connection.bind({ + portal: this.portal, + statement: this.name, + values: this.values, + binary: this.binary, + valueMap: utils.prepareValue, + }) + } catch (err) { + this.handleError(err, connection) + return } - connection.bind({ - portal: this.portal, - statement: this.name, - values: this.values, - binary: this.binary, - }) - connection.describe({ type: 'P', name: this.portal || '', From f86ae0d1c3b1148b0507d68fc154c2a400c994eb Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Thu, 16 Jul 2020 13:46:13 -0500 Subject: [PATCH 2/6] Changes --- packages/pg-protocol/src/buffer-writer.ts | 4 +- packages/pg-protocol/src/serializer.ts | 67 ++++++++++++----------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/pg-protocol/src/buffer-writer.ts b/packages/pg-protocol/src/buffer-writer.ts index 3a8d80b30..756cdc9f3 100644 --- a/packages/pg-protocol/src/buffer-writer.ts +++ b/packages/pg-protocol/src/buffer-writer.ts @@ -5,7 +5,7 @@ export class Writer { private offset: number = 5 private headerPosition: number = 0 constructor(private size = 256) { - this.buffer = Buffer.alloc(size) + this.buffer = Buffer.allocUnsafe(size) } private ensure(size: number): void { @@ -15,7 +15,7 @@ export class Writer { // exponential growth factor of around ~ 1.5 // https://stackoverflow.com/questions/2269063/buffer-growth-strategy var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size - this.buffer = Buffer.alloc(newSize) + this.buffer = Buffer.allocUnsafe(newSize) oldBuffer.copy(this.buffer) } } diff --git a/packages/pg-protocol/src/serializer.ts b/packages/pg-protocol/src/serializer.ts index bff2fd332..8a192cd84 100644 --- a/packages/pg-protocol/src/serializer.ts +++ b/packages/pg-protocol/src/serializer.ts @@ -106,50 +106,53 @@ type BindOpts = { binary?: boolean statement?: string values?: any[] + // optional map from JS value to postgres value per parameter + valueMap?: (param: any) => any } -const bind = (config: BindOpts = {}): Buffer => { - // normalize config - const portal = config.portal || '' - const statement = config.statement || '' - const binary = config.binary || false - var values = config.values || emptyArray - var len = values.length +const paramWriter = new Writer() - var useBinary = false - // TODO(bmc): all the loops in here aren't nice, we can do better - for (var j = 0; j < len; j++) { - useBinary = useBinary || values[j] instanceof Buffer - } - - var buffer = writer.addCString(portal).addCString(statement) - if (!useBinary) { - buffer.addInt16(0) - } else { - buffer.addInt16(len) - for (j = 0; j < len; j++) { - buffer.addInt16(values[j] instanceof Buffer ? 1 : 0) - } - } - buffer.addInt16(len) - for (var i = 0; i < len; i++) { +const writeValues = function (values: any[], valueMap?: (val: any) => any): void { + for (let i = 0; i < values.length; i++) { var val = values[i] if (val === null || typeof val === 'undefined') { - buffer.addInt32(-1) + writer.addInt16(0) + paramWriter.addInt32(-1) } else if (val instanceof Buffer) { - buffer.addInt32(val.length) - buffer.add(val) + writer.addInt16(1) + const mappedVal = valueMap ? valueMap(val) : val + paramWriter.addInt32(mappedVal.length) + paramWriter.add(mappedVal) } else { - buffer.addInt32(Buffer.byteLength(val)) - buffer.addString(val) + writer.addInt16(0) + const mappedVal = valueMap ? valueMap(val) : val + paramWriter.addInt32(Buffer.byteLength(mappedVal)) + paramWriter.addString(mappedVal) } } +} + +const bind = (config: BindOpts = {}): Buffer => { + // normalize config + const portal = config.portal || '' + const statement = config.statement || '' + const binary = config.binary || false + const values = config.values || emptyArray + const len = values.length + + writer.addCString(portal).addCString(statement) + writer.addInt16(len) + + writeValues(values, config.valueMap) + + writer.addInt16(len) + writer.add(paramWriter.flush()) if (binary) { - buffer.addInt16(1) // format codes to use binary - buffer.addInt16(1) + writer.addInt16(1) // format codes to use binary + writer.addInt16(1) } else { - buffer.addInt16(0) // format codes to use text + writer.addInt16(0) // format codes to use text } return writer.flush(code.bind) } From 037ba949eb7f85994ff662e68b61865b55921321 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Thu, 16 Jul 2020 13:47:58 -0500 Subject: [PATCH 3/6] Remove duplicated code from result --- packages/pg/lib/result.js | 73 --------------------------------------- 1 file changed, 73 deletions(-) diff --git a/packages/pg/lib/result.js b/packages/pg/lib/result.js index e1f6bea94..350609743 100644 --- a/packages/pg/lib/result.js +++ b/packages/pg/lib/result.js @@ -95,79 +95,6 @@ class Result { } } } - - // adds a command complete message - addCommandComplete(msg) { - var match - if (msg.text) { - // pure javascript - match = matchRegexp.exec(msg.text) - } else { - // native bindings - match = matchRegexp.exec(msg.command) - } - if (match) { - this.command = match[1] - if (match[3]) { - // COMMMAND OID ROWS - this.oid = parseInt(match[2], 10) - this.rowCount = parseInt(match[3], 10) - } else if (match[2]) { - // COMMAND ROWS - this.rowCount = parseInt(match[2], 10) - } - } - } - - _parseRowAsArray(rowData) { - var row = new Array(rowData.length) - for (var i = 0, len = rowData.length; i < len; i++) { - var rawValue = rowData[i] - if (rawValue !== null) { - row[i] = this._parsers[i](rawValue) - } else { - row[i] = null - } - } - return row - } - - parseRow(rowData) { - var row = {} - for (var i = 0, len = rowData.length; i < len; i++) { - var rawValue = rowData[i] - var field = this.fields[i].name - if (rawValue !== null) { - row[field] = this._parsers[i](rawValue) - } else { - row[field] = null - } - } - return row - } - - addRow(row) { - this.rows.push(row) - } - - addFields(fieldDescriptions) { - // clears field definitions - // multiple query statements in 1 action can result in multiple sets - // of rowDescriptions...eg: 'select NOW(); select 1::int;' - // you need to reset the fields - this.fields = fieldDescriptions - if (this.fields.length) { - this._parsers = new Array(fieldDescriptions.length) - } - for (var i = 0; i < fieldDescriptions.length; i++) { - var desc = fieldDescriptions[i] - if (this._types) { - this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text') - } else { - this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text') - } - } - } } module.exports = Result From 18919ea3489e0a40fd3fea19bba45655e15bd524 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Thu, 16 Jul 2020 13:12:26 -0500 Subject: [PATCH 4/6] Faster bind function Changes Remove duplicated code from result --- packages/pg-protocol/src/buffer-writer.ts | 4 +- packages/pg-protocol/src/serializer.ts | 67 +++++++++++---------- packages/pg/bench.js | 59 +++++++++--------- packages/pg/lib/query.js | 28 ++++----- packages/pg/lib/result.js | 73 ----------------------- 5 files changed, 82 insertions(+), 149 deletions(-) diff --git a/packages/pg-protocol/src/buffer-writer.ts b/packages/pg-protocol/src/buffer-writer.ts index 3a8d80b30..756cdc9f3 100644 --- a/packages/pg-protocol/src/buffer-writer.ts +++ b/packages/pg-protocol/src/buffer-writer.ts @@ -5,7 +5,7 @@ export class Writer { private offset: number = 5 private headerPosition: number = 0 constructor(private size = 256) { - this.buffer = Buffer.alloc(size) + this.buffer = Buffer.allocUnsafe(size) } private ensure(size: number): void { @@ -15,7 +15,7 @@ export class Writer { // exponential growth factor of around ~ 1.5 // https://stackoverflow.com/questions/2269063/buffer-growth-strategy var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size - this.buffer = Buffer.alloc(newSize) + this.buffer = Buffer.allocUnsafe(newSize) oldBuffer.copy(this.buffer) } } diff --git a/packages/pg-protocol/src/serializer.ts b/packages/pg-protocol/src/serializer.ts index bff2fd332..8a192cd84 100644 --- a/packages/pg-protocol/src/serializer.ts +++ b/packages/pg-protocol/src/serializer.ts @@ -106,50 +106,53 @@ type BindOpts = { binary?: boolean statement?: string values?: any[] + // optional map from JS value to postgres value per parameter + valueMap?: (param: any) => any } -const bind = (config: BindOpts = {}): Buffer => { - // normalize config - const portal = config.portal || '' - const statement = config.statement || '' - const binary = config.binary || false - var values = config.values || emptyArray - var len = values.length +const paramWriter = new Writer() - var useBinary = false - // TODO(bmc): all the loops in here aren't nice, we can do better - for (var j = 0; j < len; j++) { - useBinary = useBinary || values[j] instanceof Buffer - } - - var buffer = writer.addCString(portal).addCString(statement) - if (!useBinary) { - buffer.addInt16(0) - } else { - buffer.addInt16(len) - for (j = 0; j < len; j++) { - buffer.addInt16(values[j] instanceof Buffer ? 1 : 0) - } - } - buffer.addInt16(len) - for (var i = 0; i < len; i++) { +const writeValues = function (values: any[], valueMap?: (val: any) => any): void { + for (let i = 0; i < values.length; i++) { var val = values[i] if (val === null || typeof val === 'undefined') { - buffer.addInt32(-1) + writer.addInt16(0) + paramWriter.addInt32(-1) } else if (val instanceof Buffer) { - buffer.addInt32(val.length) - buffer.add(val) + writer.addInt16(1) + const mappedVal = valueMap ? valueMap(val) : val + paramWriter.addInt32(mappedVal.length) + paramWriter.add(mappedVal) } else { - buffer.addInt32(Buffer.byteLength(val)) - buffer.addString(val) + writer.addInt16(0) + const mappedVal = valueMap ? valueMap(val) : val + paramWriter.addInt32(Buffer.byteLength(mappedVal)) + paramWriter.addString(mappedVal) } } +} + +const bind = (config: BindOpts = {}): Buffer => { + // normalize config + const portal = config.portal || '' + const statement = config.statement || '' + const binary = config.binary || false + const values = config.values || emptyArray + const len = values.length + + writer.addCString(portal).addCString(statement) + writer.addInt16(len) + + writeValues(values, config.valueMap) + + writer.addInt16(len) + writer.add(paramWriter.flush()) if (binary) { - buffer.addInt16(1) // format codes to use binary - buffer.addInt16(1) + writer.addInt16(1) // format codes to use binary + writer.addInt16(1) } else { - buffer.addInt16(0) // format codes to use text + writer.addInt16(0) // format codes to use text } return writer.flush(code.bind) } diff --git a/packages/pg/bench.js b/packages/pg/bench.js index a668aa85f..5cb42ac78 100644 --- a/packages/pg/bench.js +++ b/packages/pg/bench.js @@ -45,37 +45,40 @@ const run = async () => { console.log('warmup done') const seconds = 5 - let queries = await bench(client, params, seconds * 1000) - console.log('') - console.log('little queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 733 qps') + for (let i = 0; i < 4; i++) { + let queries = await bench(client, params, seconds * 1000) + console.log('') + console.log('little queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 733 qps') - console.log('') - queries = await bench(client, seq, seconds * 1000) - console.log('sequence queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 1309 qps') + console.log('') + queries = await bench(client, seq, seconds * 1000) + console.log('sequence queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 1309 qps') - console.log('') - queries = await bench(client, insert, seconds * 1000) - console.log('insert queries:', queries) - console.log('qps', queries / seconds) - console.log('on my laptop best so far seen 6303 qps') + console.log('') + queries = await bench(client, insert, seconds * 1000) + console.log('insert queries:', queries) + console.log('qps', queries / seconds) + console.log('on my laptop best so far seen 6445 qps') - console.log('') - console.log('Warming up bytea test') - await client.query({ - text: 'INSERT INTO buf(name, data) VALUES ($1, $2)', - values: ['test', Buffer.allocUnsafe(104857600)], - }) - console.log('bytea warmup done') - const start = Date.now() - const results = await client.query('SELECT * FROM buf') - const time = Date.now() - start - console.log('bytea time:', time, 'ms') - console.log('bytea length:', results.rows[0].data.byteLength, 'bytes') - console.log('on my laptop best so far seen 1107ms and 104857600 bytes') + console.log('') + console.log('Warming up bytea test') + await client.query({ + text: 'INSERT INTO buf(name, data) VALUES ($1, $2)', + values: ['test', Buffer.allocUnsafe(104857600)], + }) + console.log('bytea warmup done') + const start = Date.now() + const results = await client.query('SELECT * FROM buf') + const time = Date.now() - start + console.log('bytea time:', time, 'ms') + console.log('bytea length:', results.rows[0].data.byteLength, 'bytes') + console.log('on my laptop best so far seen 1107ms and 104857600 bytes') + await new Promise((resolve) => setTimeout(resolve, 250)) + } await client.end() await client.end() diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 9cd0dab10..a1b061860 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -191,22 +191,22 @@ class Query extends EventEmitter { }) } - if (this.values) { - try { - this.values = this.values.map(utils.prepareValue) - } catch (err) { - this.handleError(err, connection) - return - } + // because we're mapping user supplied values to + // postgres wire protocol compatible values it could + // throw an exception, so try/catch this section + try { + connection.bind({ + portal: this.portal, + statement: this.name, + values: this.values, + binary: this.binary, + valueMap: utils.prepareValue, + }) + } catch (err) { + this.handleError(err, connection) + return } - connection.bind({ - portal: this.portal, - statement: this.name, - values: this.values, - binary: this.binary, - }) - connection.describe({ type: 'P', name: this.portal || '', diff --git a/packages/pg/lib/result.js b/packages/pg/lib/result.js index e1f6bea94..350609743 100644 --- a/packages/pg/lib/result.js +++ b/packages/pg/lib/result.js @@ -95,79 +95,6 @@ class Result { } } } - - // adds a command complete message - addCommandComplete(msg) { - var match - if (msg.text) { - // pure javascript - match = matchRegexp.exec(msg.text) - } else { - // native bindings - match = matchRegexp.exec(msg.command) - } - if (match) { - this.command = match[1] - if (match[3]) { - // COMMMAND OID ROWS - this.oid = parseInt(match[2], 10) - this.rowCount = parseInt(match[3], 10) - } else if (match[2]) { - // COMMAND ROWS - this.rowCount = parseInt(match[2], 10) - } - } - } - - _parseRowAsArray(rowData) { - var row = new Array(rowData.length) - for (var i = 0, len = rowData.length; i < len; i++) { - var rawValue = rowData[i] - if (rawValue !== null) { - row[i] = this._parsers[i](rawValue) - } else { - row[i] = null - } - } - return row - } - - parseRow(rowData) { - var row = {} - for (var i = 0, len = rowData.length; i < len; i++) { - var rawValue = rowData[i] - var field = this.fields[i].name - if (rawValue !== null) { - row[field] = this._parsers[i](rawValue) - } else { - row[field] = null - } - } - return row - } - - addRow(row) { - this.rows.push(row) - } - - addFields(fieldDescriptions) { - // clears field definitions - // multiple query statements in 1 action can result in multiple sets - // of rowDescriptions...eg: 'select NOW(); select 1::int;' - // you need to reset the fields - this.fields = fieldDescriptions - if (this.fields.length) { - this._parsers = new Array(fieldDescriptions.length) - } - for (var i = 0; i < fieldDescriptions.length; i++) { - var desc = fieldDescriptions[i] - if (this._types) { - this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text') - } else { - this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text') - } - } - } } module.exports = Result From c2e92b4fdab96df8f39882a66e76073b39543036 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Mon, 2 Nov 2020 12:17:27 -0600 Subject: [PATCH 5/6] Incorporate PR feedback --- .../src/outbound-serializer.test.ts | 4 ++ packages/pg-protocol/src/serializer.ts | 41 +++++++++++-------- packages/pg/lib/query.js | 2 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/pg-protocol/src/outbound-serializer.test.ts b/packages/pg-protocol/src/outbound-serializer.test.ts index 06f20cf9c..c917230ce 100644 --- a/packages/pg-protocol/src/outbound-serializer.test.ts +++ b/packages/pg-protocol/src/outbound-serializer.test.ts @@ -110,6 +110,10 @@ describe('serializer', () => { var expectedBuffer = new BufferList() .addCString('bang') // portal name .addCString('woo') // statement name + .addInt16(4) + .addInt16(0) + .addInt16(0) + .addInt16(0) .addInt16(0) .addInt16(4) .addInt32(1) diff --git a/packages/pg-protocol/src/serializer.ts b/packages/pg-protocol/src/serializer.ts index 8a192cd84..07e2fe498 100644 --- a/packages/pg-protocol/src/serializer.ts +++ b/packages/pg-protocol/src/serializer.ts @@ -101,31 +101,42 @@ const parse = (query: ParseOpts): Buffer => { return writer.flush(code.parse) } +type ValueMapper = (param: any, index: number) => any + type BindOpts = { portal?: string binary?: boolean statement?: string values?: any[] // optional map from JS value to postgres value per parameter - valueMap?: (param: any) => any + valueMapper?: ValueMapper } const paramWriter = new Writer() -const writeValues = function (values: any[], valueMap?: (val: any) => any): void { +// make this a const enum so typescript will inline the value +const enum ParamType { + STRING = 0, + BINARY = 1, +} + +const writeValues = function (values: any[], valueMapper?: ValueMapper): void { for (let i = 0; i < values.length; i++) { - var val = values[i] - if (val === null || typeof val === 'undefined') { - writer.addInt16(0) + const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i] + if (mappedVal == null) { + // add the param type (string) to the writer + writer.addInt16(ParamType.STRING) + // write -1 to the param writer to indicate null paramWriter.addInt32(-1) - } else if (val instanceof Buffer) { - writer.addInt16(1) - const mappedVal = valueMap ? valueMap(val) : val + } else if (mappedVal instanceof Buffer) { + // add the param type (binary) to the writer + writer.addInt16(ParamType.BINARY) + // add the buffer to the param writer paramWriter.addInt32(mappedVal.length) paramWriter.add(mappedVal) } else { - writer.addInt16(0) - const mappedVal = valueMap ? valueMap(val) : val + // add the param type (string) to the writer + writer.addInt16(ParamType.STRING) paramWriter.addInt32(Buffer.byteLength(mappedVal)) paramWriter.addString(mappedVal) } @@ -143,17 +154,13 @@ const bind = (config: BindOpts = {}): Buffer => { writer.addCString(portal).addCString(statement) writer.addInt16(len) - writeValues(values, config.valueMap) + writeValues(values, config.valueMapper) writer.addInt16(len) writer.add(paramWriter.flush()) - if (binary) { - writer.addInt16(1) // format codes to use binary - writer.addInt16(1) - } else { - writer.addInt16(0) // format codes to use text - } + // format code + writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING) return writer.flush(code.bind) } diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 619e46790..c0dfedd1e 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -206,7 +206,7 @@ class Query extends EventEmitter { statement: this.name, values: this.values, binary: this.binary, - valueMap: utils.prepareValue, + valueMapper: utils.prepareValue, }) } catch (err) { this.handleError(err, connection) From be8d7a16cae1b71212037e299bd558378b0a1d81 Mon Sep 17 00:00:00 2001 From: "Brian M. Carlson" Date: Mon, 2 Nov 2020 12:33:21 -0600 Subject: [PATCH 6/6] Add another test for custom mapping --- .../src/outbound-serializer.test.ts | 25 +++++++++++++++++++ packages/pg/lib/utils.js | 7 +++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/pg-protocol/src/outbound-serializer.test.ts b/packages/pg-protocol/src/outbound-serializer.test.ts index c917230ce..f6669becd 100644 --- a/packages/pg-protocol/src/outbound-serializer.test.ts +++ b/packages/pg-protocol/src/outbound-serializer.test.ts @@ -129,6 +129,31 @@ describe('serializer', () => { }) }) + it('with custom valueMapper', function () { + const actual = serialize.bind({ + portal: 'bang', + statement: 'woo', + values: ['1', 'hi', null, 'zing'], + valueMapper: () => null, + }) + var expectedBuffer = new BufferList() + .addCString('bang') // portal name + .addCString('woo') // statement name + .addInt16(4) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(0) + .addInt16(4) + .addInt32(-1) + .addInt32(-1) + .addInt32(-1) + .addInt32(-1) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + it('with named statement, portal, and buffer value', function () { const actual = serialize.bind({ portal: 'bang', diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index b3b4ff4c1..d63fe68f1 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -38,6 +38,10 @@ function arrayString(val) { // note: you can override this function to provide your own conversion mechanism // for complex types, etc... var prepareValue = function (val, seen) { + // null and undefined are both null for postgres + if (val == null) { + return null + } if (val instanceof Buffer) { return val } @@ -58,9 +62,6 @@ var prepareValue = function (val, seen) { if (Array.isArray(val)) { return arrayString(val) } - if (val === null || typeof val === 'undefined') { - return null - } if (typeof val === 'object') { return prepareObject(val, seen) }