Skip to content

Commit 18919ea

Browse files
committed
Faster bind function
Changes Remove duplicated code from result
1 parent 8291b23 commit 18919ea

File tree

5 files changed

+82
-149
lines changed

5 files changed

+82
-149
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class Writer {
55
private offset: number = 5
66
private headerPosition: number = 0
77
constructor(private size = 256) {
8-
this.buffer = Buffer.alloc(size)
8+
this.buffer = Buffer.allocUnsafe(size)
99
}
1010

1111
private ensure(size: number): void {
@@ -15,7 +15,7 @@ export class Writer {
1515
// exponential growth factor of around ~ 1.5
1616
// https://stackoverflow.com/questions/2269063/buffer-growth-strategy
1717
var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size
18-
this.buffer = Buffer.alloc(newSize)
18+
this.buffer = Buffer.allocUnsafe(newSize)
1919
oldBuffer.copy(this.buffer)
2020
}
2121
}

packages/pg-protocol/src/serializer.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -106,50 +106,53 @@ type BindOpts = {
106106
binary?: boolean
107107
statement?: string
108108
values?: any[]
109+
// optional map from JS value to postgres value per parameter
110+
valueMap?: (param: any) => any
109111
}
110112

111-
const bind = (config: BindOpts = {}): Buffer => {
112-
// normalize config
113-
const portal = config.portal || ''
114-
const statement = config.statement || ''
115-
const binary = config.binary || false
116-
var values = config.values || emptyArray
117-
var len = values.length
113+
const paramWriter = new Writer()
118114

119-
var useBinary = false
120-
// TODO(bmc): all the loops in here aren't nice, we can do better
121-
for (var j = 0; j < len; j++) {
122-
useBinary = useBinary || values[j] instanceof Buffer
123-
}
124-
125-
var buffer = writer.addCString(portal).addCString(statement)
126-
if (!useBinary) {
127-
buffer.addInt16(0)
128-
} else {
129-
buffer.addInt16(len)
130-
for (j = 0; j < len; j++) {
131-
buffer.addInt16(values[j] instanceof Buffer ? 1 : 0)
132-
}
133-
}
134-
buffer.addInt16(len)
135-
for (var i = 0; i < len; i++) {
115+
const writeValues = function (values: any[], valueMap?: (val: any) => any): void {
116+
for (let i = 0; i < values.length; i++) {
136117
var val = values[i]
137118
if (val === null || typeof val === 'undefined') {
138-
buffer.addInt32(-1)
119+
writer.addInt16(0)
120+
paramWriter.addInt32(-1)
139121
} else if (val instanceof Buffer) {
140-
buffer.addInt32(val.length)
141-
buffer.add(val)
122+
writer.addInt16(1)
123+
const mappedVal = valueMap ? valueMap(val) : val
124+
paramWriter.addInt32(mappedVal.length)
125+
paramWriter.add(mappedVal)
142126
} else {
143-
buffer.addInt32(Buffer.byteLength(val))
144-
buffer.addString(val)
127+
writer.addInt16(0)
128+
const mappedVal = valueMap ? valueMap(val) : val
129+
paramWriter.addInt32(Buffer.byteLength(mappedVal))
130+
paramWriter.addString(mappedVal)
145131
}
146132
}
133+
}
134+
135+
const bind = (config: BindOpts = {}): Buffer => {
136+
// normalize config
137+
const portal = config.portal || ''
138+
const statement = config.statement || ''
139+
const binary = config.binary || false
140+
const values = config.values || emptyArray
141+
const len = values.length
142+
143+
writer.addCString(portal).addCString(statement)
144+
writer.addInt16(len)
145+
146+
writeValues(values, config.valueMap)
147+
148+
writer.addInt16(len)
149+
writer.add(paramWriter.flush())
147150

148151
if (binary) {
149-
buffer.addInt16(1) // format codes to use binary
150-
buffer.addInt16(1)
152+
writer.addInt16(1) // format codes to use binary
153+
writer.addInt16(1)
151154
} else {
152-
buffer.addInt16(0) // format codes to use text
155+
writer.addInt16(0) // format codes to use text
153156
}
154157
return writer.flush(code.bind)
155158
}

packages/pg/bench.js

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,37 +45,40 @@ const run = async () => {
4545
console.log('warmup done')
4646
const seconds = 5
4747

48-
let queries = await bench(client, params, seconds * 1000)
49-
console.log('')
50-
console.log('little queries:', queries)
51-
console.log('qps', queries / seconds)
52-
console.log('on my laptop best so far seen 733 qps')
48+
for (let i = 0; i < 4; i++) {
49+
let queries = await bench(client, params, seconds * 1000)
50+
console.log('')
51+
console.log('little queries:', queries)
52+
console.log('qps', queries / seconds)
53+
console.log('on my laptop best so far seen 733 qps')
5354

54-
console.log('')
55-
queries = await bench(client, seq, seconds * 1000)
56-
console.log('sequence queries:', queries)
57-
console.log('qps', queries / seconds)
58-
console.log('on my laptop best so far seen 1309 qps')
55+
console.log('')
56+
queries = await bench(client, seq, seconds * 1000)
57+
console.log('sequence queries:', queries)
58+
console.log('qps', queries / seconds)
59+
console.log('on my laptop best so far seen 1309 qps')
5960

60-
console.log('')
61-
queries = await bench(client, insert, seconds * 1000)
62-
console.log('insert queries:', queries)
63-
console.log('qps', queries / seconds)
64-
console.log('on my laptop best so far seen 6303 qps')
61+
console.log('')
62+
queries = await bench(client, insert, seconds * 1000)
63+
console.log('insert queries:', queries)
64+
console.log('qps', queries / seconds)
65+
console.log('on my laptop best so far seen 6445 qps')
6566

66-
console.log('')
67-
console.log('Warming up bytea test')
68-
await client.query({
69-
text: 'INSERT INTO buf(name, data) VALUES ($1, $2)',
70-
values: ['test', Buffer.allocUnsafe(104857600)],
71-
})
72-
console.log('bytea warmup done')
73-
const start = Date.now()
74-
const results = await client.query('SELECT * FROM buf')
75-
const time = Date.now() - start
76-
console.log('bytea time:', time, 'ms')
77-
console.log('bytea length:', results.rows[0].data.byteLength, 'bytes')
78-
console.log('on my laptop best so far seen 1107ms and 104857600 bytes')
67+
console.log('')
68+
console.log('Warming up bytea test')
69+
await client.query({
70+
text: 'INSERT INTO buf(name, data) VALUES ($1, $2)',
71+
values: ['test', Buffer.allocUnsafe(104857600)],
72+
})
73+
console.log('bytea warmup done')
74+
const start = Date.now()
75+
const results = await client.query('SELECT * FROM buf')
76+
const time = Date.now() - start
77+
console.log('bytea time:', time, 'ms')
78+
console.log('bytea length:', results.rows[0].data.byteLength, 'bytes')
79+
console.log('on my laptop best so far seen 1107ms and 104857600 bytes')
80+
await new Promise((resolve) => setTimeout(resolve, 250))
81+
}
7982

8083
await client.end()
8184
await client.end()

packages/pg/lib/query.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -191,22 +191,22 @@ class Query extends EventEmitter {
191191
})
192192
}
193193

194-
if (this.values) {
195-
try {
196-
this.values = this.values.map(utils.prepareValue)
197-
} catch (err) {
198-
this.handleError(err, connection)
199-
return
200-
}
194+
// because we're mapping user supplied values to
195+
// postgres wire protocol compatible values it could
196+
// throw an exception, so try/catch this section
197+
try {
198+
connection.bind({
199+
portal: this.portal,
200+
statement: this.name,
201+
values: this.values,
202+
binary: this.binary,
203+
valueMap: utils.prepareValue,
204+
})
205+
} catch (err) {
206+
this.handleError(err, connection)
207+
return
201208
}
202209

203-
connection.bind({
204-
portal: this.portal,
205-
statement: this.name,
206-
values: this.values,
207-
binary: this.binary,
208-
})
209-
210210
connection.describe({
211211
type: 'P',
212212
name: this.portal || '',

packages/pg/lib/result.js

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -95,79 +95,6 @@ class Result {
9595
}
9696
}
9797
}
98-
99-
// adds a command complete message
100-
addCommandComplete(msg) {
101-
var match
102-
if (msg.text) {
103-
// pure javascript
104-
match = matchRegexp.exec(msg.text)
105-
} else {
106-
// native bindings
107-
match = matchRegexp.exec(msg.command)
108-
}
109-
if (match) {
110-
this.command = match[1]
111-
if (match[3]) {
112-
// COMMMAND OID ROWS
113-
this.oid = parseInt(match[2], 10)
114-
this.rowCount = parseInt(match[3], 10)
115-
} else if (match[2]) {
116-
// COMMAND ROWS
117-
this.rowCount = parseInt(match[2], 10)
118-
}
119-
}
120-
}
121-
122-
_parseRowAsArray(rowData) {
123-
var row = new Array(rowData.length)
124-
for (var i = 0, len = rowData.length; i < len; i++) {
125-
var rawValue = rowData[i]
126-
if (rawValue !== null) {
127-
row[i] = this._parsers[i](rawValue)
128-
} else {
129-
row[i] = null
130-
}
131-
}
132-
return row
133-
}
134-
135-
parseRow(rowData) {
136-
var row = {}
137-
for (var i = 0, len = rowData.length; i < len; i++) {
138-
var rawValue = rowData[i]
139-
var field = this.fields[i].name
140-
if (rawValue !== null) {
141-
row[field] = this._parsers[i](rawValue)
142-
} else {
143-
row[field] = null
144-
}
145-
}
146-
return row
147-
}
148-
149-
addRow(row) {
150-
this.rows.push(row)
151-
}
152-
153-
addFields(fieldDescriptions) {
154-
// clears field definitions
155-
// multiple query statements in 1 action can result in multiple sets
156-
// of rowDescriptions...eg: 'select NOW(); select 1::int;'
157-
// you need to reset the fields
158-
this.fields = fieldDescriptions
159-
if (this.fields.length) {
160-
this._parsers = new Array(fieldDescriptions.length)
161-
}
162-
for (var i = 0; i < fieldDescriptions.length; i++) {
163-
var desc = fieldDescriptions[i]
164-
if (this._types) {
165-
this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text')
166-
} else {
167-
this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text')
168-
}
169-
}
170-
}
17198
}
17299

173100
module.exports = Result

0 commit comments

Comments
 (0)