Skip to content

Commit 69f30df

Browse files
authored
Merge pull request #2030 from brianc/bmc/add-pg-cursor
Add pg cursor
2 parents ebb81db + b14cf67 commit 69f30df

18 files changed

+1196
-161
lines changed

.eslintrc

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
{
2-
"plugins": [
3-
"node"
4-
],
5-
"extends": [
6-
"standard",
7-
"eslint:recommended",
8-
"plugin:node/recommended"
9-
],
2+
"plugins": ["node"],
3+
"extends": ["standard", "eslint:recommended", "plugin:node/recommended"],
104
"parserOptions": {
115
"ecmaVersion": 2017
126
},
137
"env": {
148
"node": true,
15-
"es6": true
9+
"es6": true,
10+
"mocha": true
1611
},
1712
"rules": {
13+
"space-before-function-paren": "off",
14+
"node/no-unpublished-require": [
15+
"error",
16+
{
17+
"allowModules": ["pg"]
18+
}
19+
]
1820
}
1921
}

.travis.yml

+1-5
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ matrix:
3636
addons:
3737
postgresql: "9.6"
3838

39-
# PostgreSQL 9.1 and 9.2 only work on precise
40-
- node_js: lts/carbon
41-
addons:
42-
postgresql: "9.1"
43-
dist: precise
39+
# PostgreSQL 9.2 only works on precise
4440
- node_js: lts/carbon
4541
addons:
4642
postgresql: "9.2"

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"packages/*"
1111
],
1212
"scripts": {
13-
"test": "yarn lerna exec --parallel yarn test"
13+
"test": "yarn lerna exec --parallel yarn test",
14+
"lint": "yarn lerna exec --parallel yarn lint"
1415
},
1516
"devDependencies": {
1617
"lerna": "^3.19.0"

packages/pg-cursor/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

packages/pg-cursor/Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.PHONY: test
2+
test:
3+
npm test
4+
5+
.PHONY: patch
6+
patch: test
7+
npm version patch -m "Bump version"
8+
git push origin master --tags
9+
npm publish
10+
11+
.PHONY: minor
12+
minor: test
13+
npm version minor -m "Bump version"
14+
git push origin master --tags
15+
npm publish

packages/pg-cursor/README.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
node-pg-cursor
2+
==============
3+
4+
Use a PostgreSQL result cursor from node with an easy to use API.
5+
6+
### install
7+
8+
```sh
9+
$ npm install pg-cursor
10+
```
11+
___note___: this depends on _either_ `npm install pg` or `npm install pg.js`, but you __must__ be using the pure JavaScript client. This will __not work__ with the native bindings.
12+
13+
### :star: [Documentation](https://node-postgres.com/api/cursor) :star:
14+
15+
### license
16+
17+
The MIT License (MIT)
18+
19+
Copyright (c) 2013 Brian M. Carlson
20+
21+
Permission is hereby granted, free of charge, to any person obtaining a copy
22+
of this software and associated documentation files (the "Software"), to deal
23+
in the Software without restriction, including without limitation the rights
24+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25+
copies of the Software, and to permit persons to whom the Software is
26+
furnished to do so, subject to the following conditions:
27+
28+
The above copyright notice and this permission notice shall be included in
29+
all copies or substantial portions of the Software.
30+
31+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37+
THE SOFTWARE.

packages/pg-cursor/index.js

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
'use strict'
2+
const Result = require('pg/lib/result.js')
3+
const prepare = require('pg/lib/utils.js').prepareValue
4+
const EventEmitter = require('events').EventEmitter
5+
const util = require('util')
6+
7+
let nextUniqueID = 1 // concept borrowed from org.postgresql.core.v3.QueryExecutorImpl
8+
9+
function Cursor (text, values, config) {
10+
EventEmitter.call(this)
11+
12+
this._conf = config || {}
13+
this.text = text
14+
this.values = values ? values.map(prepare) : null
15+
this.connection = null
16+
this._queue = []
17+
this.state = 'initialized'
18+
this._result = new Result(this._conf.rowMode, this._conf.types)
19+
this._cb = null
20+
this._rows = null
21+
this._portal = null
22+
this._ifNoData = this._ifNoData.bind(this)
23+
this._rowDescription = this._rowDescription.bind(this)
24+
}
25+
26+
util.inherits(Cursor, EventEmitter)
27+
28+
Cursor.prototype._ifNoData = function () {
29+
this.state = 'idle'
30+
this._shiftQueue()
31+
}
32+
33+
Cursor.prototype._rowDescription = function () {
34+
if (this.connection) {
35+
this.connection.removeListener('noData', this._ifNoData)
36+
}
37+
}
38+
39+
Cursor.prototype.submit = function (connection) {
40+
this.connection = connection
41+
this._portal = 'C_' + nextUniqueID++
42+
43+
const con = connection
44+
45+
con.parse(
46+
{
47+
text: this.text
48+
},
49+
true
50+
)
51+
52+
con.bind(
53+
{
54+
portal: this._portal,
55+
values: this.values
56+
},
57+
true
58+
)
59+
60+
con.describe(
61+
{
62+
type: 'P',
63+
name: this._portal // AWS Redshift requires a portal name
64+
},
65+
true
66+
)
67+
68+
con.flush()
69+
70+
if (this._conf.types) {
71+
this._result._getTypeParser = this._conf.types.getTypeParser
72+
}
73+
74+
con.once('noData', this._ifNoData)
75+
con.once('rowDescription', this._rowDescription)
76+
}
77+
78+
Cursor.prototype._shiftQueue = function () {
79+
if (this._queue.length) {
80+
this._getRows.apply(this, this._queue.shift())
81+
}
82+
}
83+
84+
Cursor.prototype._closePortal = function () {
85+
// because we opened a named portal to stream results
86+
// we need to close the same named portal. Leaving a named portal
87+
// open can lock tables for modification if inside a transaction.
88+
// see https://github.com/brianc/node-pg-cursor/issues/56
89+
this.connection.close({ type: 'P', name: this._portal })
90+
this.connection.sync()
91+
}
92+
93+
Cursor.prototype.handleRowDescription = function (msg) {
94+
this._result.addFields(msg.fields)
95+
this.state = 'idle'
96+
this._shiftQueue()
97+
}
98+
99+
Cursor.prototype.handleDataRow = function (msg) {
100+
const row = this._result.parseRow(msg.fields)
101+
this.emit('row', row, this._result)
102+
this._rows.push(row)
103+
}
104+
105+
Cursor.prototype._sendRows = function () {
106+
this.state = 'idle'
107+
setImmediate(() => {
108+
const cb = this._cb
109+
// remove callback before calling it
110+
// because likely a new one will be added
111+
// within the call to this callback
112+
this._cb = null
113+
if (cb) {
114+
this._result.rows = this._rows
115+
cb(null, this._rows, this._result)
116+
}
117+
this._rows = []
118+
})
119+
}
120+
121+
Cursor.prototype.handleCommandComplete = function (msg) {
122+
this._result.addCommandComplete(msg)
123+
this._closePortal()
124+
}
125+
126+
Cursor.prototype.handlePortalSuspended = function () {
127+
this._sendRows()
128+
}
129+
130+
Cursor.prototype.handleReadyForQuery = function () {
131+
this._sendRows()
132+
this.state = 'done'
133+
this.emit('end', this._result)
134+
}
135+
136+
Cursor.prototype.handleEmptyQuery = function () {
137+
this.connection.sync()
138+
}
139+
140+
Cursor.prototype.handleError = function (msg) {
141+
this.connection.removeListener('noData', this._ifNoData)
142+
this.connection.removeListener('rowDescription', this._rowDescription)
143+
this.state = 'error'
144+
this._error = msg
145+
// satisfy any waiting callback
146+
if (this._cb) {
147+
this._cb(msg)
148+
}
149+
// dispatch error to all waiting callbacks
150+
for (let i = 0; i < this._queue.length; i++) {
151+
this._queue.pop()[1](msg)
152+
}
153+
154+
if (this.listenerCount('error') > 0) {
155+
// only dispatch error events if we have a listener
156+
this.emit('error', msg)
157+
}
158+
// call sync to keep this connection from hanging
159+
this.connection.sync()
160+
}
161+
162+
Cursor.prototype._getRows = function (rows, cb) {
163+
this.state = 'busy'
164+
this._cb = cb
165+
this._rows = []
166+
const msg = {
167+
portal: this._portal,
168+
rows: rows
169+
}
170+
this.connection.execute(msg, true)
171+
this.connection.flush()
172+
}
173+
174+
// users really shouldn't be calling 'end' here and terminating a connection to postgres
175+
// via the low level connection.end api
176+
Cursor.prototype.end = util.deprecate(function (cb) {
177+
if (this.state !== 'initialized') {
178+
this.connection.sync()
179+
}
180+
this.connection.once('end', cb)
181+
this.connection.end()
182+
}, 'Cursor.end is deprecated. Call end on the client itself to end a connection to the database.')
183+
184+
Cursor.prototype.close = function (cb) {
185+
if (this.state === 'done') {
186+
if (cb) {
187+
return setImmediate(cb)
188+
} else {
189+
return
190+
}
191+
}
192+
this._closePortal()
193+
this.state = 'done'
194+
if (cb) {
195+
this.connection.once('closeComplete', function () {
196+
cb()
197+
})
198+
}
199+
}
200+
201+
Cursor.prototype.read = function (rows, cb) {
202+
if (this.state === 'idle') {
203+
return this._getRows(rows, cb)
204+
}
205+
if (this.state === 'busy' || this.state === 'initialized') {
206+
return this._queue.push([rows, cb])
207+
}
208+
if (this.state === 'error') {
209+
return setImmediate(() => cb(this._error))
210+
}
211+
if (this.state === 'done') {
212+
return setImmediate(() => cb(null, []))
213+
} else {
214+
throw new Error('Unknown state: ' + this.state)
215+
}
216+
}
217+
218+
module.exports = Cursor

packages/pg-cursor/package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "pg-cursor",
3+
"version": "2.0.1",
4+
"description": "Query cursor extension for node-postgres",
5+
"main": "index.js",
6+
"directories": {
7+
"test": "test"
8+
},
9+
"scripts": {
10+
"test": "mocha && eslint .",
11+
"lint": "eslint ."
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "git://github.com/brianc/node-pg-cursor.git"
16+
},
17+
"author": "Brian M. Carlson",
18+
"license": "MIT",
19+
"devDependencies": {
20+
"eslint": "^6.5.1",
21+
"eslint-config-prettier": "^6.4.0",
22+
"eslint-plugin-prettier": "^3.1.1",
23+
"mocha": "^6.2.2",
24+
"pg": "7.x",
25+
"prettier": "^1.18.2"
26+
},
27+
"prettier": {
28+
"semi": false,
29+
"printWidth": 120,
30+
"trailingComma": "none",
31+
"singleQuote": true
32+
}
33+
}

0 commit comments

Comments
 (0)