Skip to content

Commit 7d7ca02

Browse files
committed
faster row parsing
1 parent 76bf45d commit 7d7ca02

File tree

2 files changed

+47
-66
lines changed

2 files changed

+47
-66
lines changed

packages/pg/lib/query.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Query {
2323

2424
// potential for multiple results
2525
this._results = this._result
26-
this._canceledDueToError = false
26+
this._canceledDueToError = null
2727
}
2828

2929
get text() {
@@ -113,10 +113,8 @@ class Query {
113113
this._canceledDueToError = err
114114
return
115115
}
116-
117-
//if (this.callback) {
118-
this._result.addRow(row)
119-
//}
116+
117+
this._result.addRow(row)
120118
}
121119

122120
handleCommandComplete(msg, connection) {

packages/pg/lib/result.js

Lines changed: 44 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,19 @@ var types = require('pg-types')
44

55
var matchRegexp = /^([A-Za-z]+)(?: (\d+))?(?: (\d+))?/
66

7+
let parserCache = new Map()
8+
79
// result object returned from query
810
// in the 'end' event and also
911
// passed as second argument to provided callback
1012
class Result {
11-
constructor(rowMode, types) {
13+
constructor(_rowMode, types) {
1214
this.command = null
1315
this.rowCount = null
1416
this.oid = null
1517
this.rows = []
1618
this.fields = []
17-
this._parsers = undefined
1819
this._types = types
19-
const rowAsArray = rowMode === 'array'
20-
if (rowAsArray) {
21-
this.parseRow = this._parseRowAsArray
22-
}
23-
this._prebuiltEmptyResultObject = null
2420
}
2521

2622
clear(){
@@ -29,82 +25,69 @@ class Result {
2925
this.oid = null
3026
this.rows = []
3127
this.fields = []
32-
this._parsers = undefined
3328
}
3429

3530
// adds a command complete message
3631
addCommandComplete(msg) {
37-
var match
38-
if (msg.text) {
39-
// pure javascript
40-
match = matchRegexp.exec(msg.text)
41-
} else {
42-
// native bindings
43-
match = matchRegexp.exec(msg.command)
44-
}
45-
if (match) {
46-
this.command = match[1]
47-
if (match[3]) {
48-
// COMMMAND OID ROWS
49-
this.oid = parseInt(match[2], 10)
50-
this.rowCount = parseInt(match[3], 10)
51-
} else if (match[2]) {
52-
// COMMAND ROWS
53-
this.rowCount = parseInt(match[2], 10)
54-
}
55-
}
56-
}
57-
58-
_parseRowAsArray(rowData) {
59-
var row = new Array(rowData.length)
60-
for (var i = 0, len = rowData.length; i < len; i++) {
61-
var rawValue = rowData[i]
62-
if (rawValue !== null) {
63-
row[i] = this._parsers[i](rawValue)
64-
} else {
65-
row[i] = null
66-
}
32+
var match = matchRegexp.exec(msg.text ?? msg.command)
33+
if (!match) return
34+
this.command = match[1]
35+
if (match[3]) {
36+
// COMMMAND OID ROWS
37+
this.oid = parseInt(match[2], 10)
38+
this.rowCount = parseInt(match[3], 10)
39+
} else if (match[2]) {
40+
// COMMAND ROWS
41+
this.rowCount = parseInt(match[2], 10)
6742
}
68-
return row
69-
}
70-
71-
parseRow(rowData) {
72-
let row = { ...this._prebuiltEmptyResultObject }
73-
for (let i = 0, len = rowData.length; i < len; i++) {
74-
let rawValue = rowData[i]
75-
if (rawValue === null) continue
76-
row[this.fields[i].name] = this._parsers[i](rawValue)
77-
}
78-
return row
7943
}
8044

8145
addRow(row) {
8246
this.rows.push(row)
8347
}
8448

49+
static _p(p, v) {
50+
return v === null ? null : p(v)
51+
}
52+
8553
addFields(fieldDescriptions) {
8654
// clears field definitions
8755
// multiple query statements in 1 action can result in multiple sets
8856
// of rowDescriptions...eg: 'select NOW(); select 1::int;'
8957
// you need to reset the fields
9058
this.fields = fieldDescriptions
91-
if (this.fields.length) {
92-
this._parsers = new Array(fieldDescriptions.length)
93-
}
9459

95-
var row = {}
60+
let localTypes = this._types || types
9661

97-
for (var i = 0; i < fieldDescriptions.length; i++) {
98-
var desc = fieldDescriptions[i]
99-
row[desc.name] = null
62+
let parseFn
63+
const cacheKey = fieldDescriptions.map(desc => desc.dataTypeID + "|" + desc.name).join(',')
64+
parseFn = parserCache.get(cacheKey)
65+
if(!parseFn) {
66+
parseFn = 'return function(rowData){return {'
67+
let args = [], args2 = []
68+
for (let i = 0; i < fieldDescriptions.length; i++) {
69+
let desc = fieldDescriptions[i]
70+
71+
const parser = localTypes.getTypeParser(desc.dataTypeID, desc.format || 'text')
72+
if(parser === String) {
73+
parseFn += `${JSON.stringify(desc.name)}: rowData[${i}],`
74+
} else {
75+
parseFn += `${JSON.stringify(desc.name)}: _p(a${i},rowData[${i}]),`
76+
args.push('a' + i)
77+
args2.push(parser)
78+
}
79+
}
10080

101-
if (this._types) {
102-
this._parsers[i] = this._types.getTypeParser(desc.dataTypeID, desc.format || 'text')
103-
} else {
104-
this._parsers[i] = types.getTypeParser(desc.dataTypeID, desc.format || 'text')
81+
parseFn += '}}'
82+
parseFn = new Function('_p', ...args, parseFn)
83+
parseFn = parseFn(Result._p, ...args2)
84+
if(parserCache.size > 256) {
85+
// prevent unbounded memory growth
86+
parserCache.clear()
10587
}
88+
parserCache.set(cacheKey, parseFn)
10689
}
107-
this._prebuiltEmptyResultObject = row
90+
this.parseRow = parseFn
10891
}
10992
}
11093

0 commit comments

Comments
 (0)