Skip to content

Commit d2158b7

Browse files
authored
Merge pull request #24 from cemremengu/transaction-helper
Added transaction helper
2 parents e39d415 + f45b1a6 commit d2158b7

File tree

4 files changed

+201
-2
lines changed

4 files changed

+201
-2
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ node_js:
88
services:
99
- postgresql
1010

11+
before_script:
12+
- psql -d postgres -c 'CREATE TABLE users(id serial PRIMARY KEY, username VARCHAR (50) NOT NULL);' -U postgres
13+
1114
notifications:
1215
email:
1316
on_success: never

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ This plugin will add the `pg` namespace in your Fastify instance, with the follo
1818
connect: the function to get a connection from the pool
1919
pool: the pool instance
2020
Client: a client constructor for a single query
21-
query: an utility to perform a query without a transaction
21+
query: a utility to perform a query _without_ a transaction
22+
transact: a utility to perform multiple queries _with_ a transaction
2223
```
2324

2425
Example:
@@ -95,6 +96,54 @@ fastify.listen(3000, err => {
9596
console.log(`server listening on ${fastify.server.address().port}`)
9697
})
9798
```
99+
100+
Use of `pg.transact`
101+
```js
102+
const fastify = require('fastify')
103+
104+
fastify.register(require('fastify-postgres'), {
105+
connectionString: 'postgres://postgres@localhost/postgres'
106+
})
107+
108+
fastify.post('/user/:username', (req, reply) => {
109+
fastify.pg.transact(async client => {
110+
try {
111+
const id = await client.query('INSERT INTO users(username) VALUES($1) RETURNING id', [req.params.username])
112+
reply.send(id)
113+
} catch (err) {
114+
reply.send(err)
115+
}
116+
})
117+
})
118+
119+
/* or with a transaction callback
120+
121+
fastify.pg.transact(client => {
122+
return client.query('INSERT INTO users(username) VALUES($1) RETURNING id', [req.params.username])
123+
},
124+
function onResult (err, result) {
125+
reply.send(err || result)
126+
}
127+
})
128+
129+
*/
130+
131+
/* or with a commit callback
132+
133+
fastify.pg.transact((client, commit) => {
134+
client.query('INSERT INTO users(username) VALUES($1) RETURNING id', [req.params.username], (err, id) => {
135+
commit(err, id)
136+
});
137+
})
138+
139+
*/
140+
141+
fastify.listen(3000, err => {
142+
if (err) throw err
143+
console.log(`server listening on ${fastify.server.address().port}`)
144+
})
145+
```
146+
98147
As you can see there is no need to close the client, since is done internally. Promises and async await are supported as well.
99148

100149
### Native option

index.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,58 @@
33
const fp = require('fastify-plugin')
44
var pg = require('pg')
55

6+
function transactionUtil (pool, fn, cb) {
7+
pool.connect((err, client, done) => {
8+
if (err) return cb(err)
9+
10+
const shouldAbort = (err) => {
11+
if (err) {
12+
client.query('ROLLBACK', () => {
13+
done()
14+
})
15+
}
16+
return !!err
17+
}
18+
19+
const commit = (err, res) => {
20+
if (shouldAbort(err)) return cb(err)
21+
22+
client.query('COMMIT', (err) => {
23+
done()
24+
if (err) {
25+
return cb(err)
26+
}
27+
return cb(null, res)
28+
})
29+
}
30+
31+
client.query('BEGIN', (err) => {
32+
if (shouldAbort(err)) return cb(err)
33+
34+
const promise = fn(client, commit)
35+
36+
if (promise && typeof promise.then === 'function') {
37+
promise.then(
38+
(res) => commit(null, res),
39+
(e) => commit(e))
40+
}
41+
})
42+
})
43+
}
44+
45+
function transact (fn, cb) {
46+
if (cb && typeof cb === 'function') {
47+
return transactionUtil(this, fn, cb)
48+
}
49+
50+
return new Promise((resolve, reject) => {
51+
transactionUtil(this, fn, function (err, res) {
52+
if (err) { return reject(err) }
53+
return resolve(res)
54+
})
55+
})
56+
}
57+
658
function fastifyPostgres (fastify, options, next) {
759
if (options.native) {
860
delete options.native
@@ -21,7 +73,8 @@ function fastifyPostgres (fastify, options, next) {
2173
connect: pool.connect.bind(pool),
2274
pool: pool,
2375
Client: pg.Client,
24-
query: pool.query.bind(pool)
76+
query: pool.query.bind(pool),
77+
transact: transact.bind(pool)
2578
}
2679

2780
if (name) {

test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,97 @@ test('fastify.pg.test should throw with duplicate connection names', t => {
253253
t.is(err.message, 'Connection name has already been registered: test')
254254
})
255255
})
256+
257+
test('fastify.pg.test use transact util with promise', t => {
258+
t.plan(3)
259+
260+
const fastify = Fastify()
261+
t.tearDown(fastify.close.bind(fastify))
262+
263+
fastify.register(fastifyPostgres, {
264+
name: 'test',
265+
connectionString: 'postgres://postgres@localhost/postgres'
266+
})
267+
268+
fastify.ready(err => {
269+
t.error(err)
270+
fastify.pg.test
271+
.transact(client => client.query('INSERT INTO users(username) VALUES($1) RETURNING id', ['with-promise']))
272+
.then(result => {
273+
t.equals(result.rows.length, 1)
274+
fastify.pg.test
275+
.query(`SELECT * FROM users WHERE username = 'with-promise'`)
276+
.then(result => {
277+
t.ok(result.rows[0].username === 'with-promise')
278+
}).catch(err => {
279+
t.fail(err)
280+
})
281+
})
282+
.catch(err => {
283+
t.fail(err)
284+
})
285+
})
286+
})
287+
288+
test('fastify.pg.test use transact util with callback', t => {
289+
t.plan(4)
290+
291+
const fastify = Fastify()
292+
t.tearDown(fastify.close.bind(fastify))
293+
294+
fastify.register(fastifyPostgres, {
295+
name: 'test',
296+
connectionString: 'postgres://postgres@localhost/postgres'
297+
})
298+
299+
fastify.ready(err => {
300+
t.error(err)
301+
302+
fastify.pg.test
303+
.transact(client => client.query('INSERT INTO users(username) VALUES($1) RETURNING id', ['with-callback']), function (err, res) {
304+
t.error(err)
305+
t.equals(res.rows.length, 1)
306+
307+
fastify.pg.test
308+
.query(`SELECT * FROM users WHERE username = 'with-callback'`)
309+
.then(result => {
310+
t.ok(result.rows[0].username === 'with-callback')
311+
}).catch(err => {
312+
t.fail(err)
313+
})
314+
})
315+
})
316+
})
317+
318+
test('fastify.pg.test use transact util with commit callback', t => {
319+
t.plan(4)
320+
321+
const fastify = Fastify()
322+
t.tearDown(fastify.close.bind(fastify))
323+
324+
fastify.register(fastifyPostgres, {
325+
name: 'test',
326+
connectionString: 'postgres://postgres@localhost/postgres'
327+
})
328+
329+
fastify.ready(err => {
330+
t.error(err)
331+
332+
fastify.pg.test.transact((client, commit) => {
333+
client.query('INSERT INTO users(username) VALUES($1) RETURNING id', ['commit-callback'], (err, id) => {
334+
commit(err, id)
335+
})
336+
}, function (err, res) {
337+
t.error(err)
338+
t.equals(res.rows.length, 1)
339+
340+
fastify.pg.test
341+
.query(`SELECT * FROM users WHERE username = 'commit-callback'`)
342+
.then(result => {
343+
t.ok(result.rows[0].username === 'commit-callback')
344+
}).catch(err => {
345+
t.fail(err)
346+
})
347+
})
348+
})
349+
})

0 commit comments

Comments
 (0)