Skip to content

Commit 4db8114

Browse files
committed
add support for transcations
1 parent 9f03ecb commit 4db8114

File tree

4 files changed

+2112
-3089
lines changed

4 files changed

+2112
-3089
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A Postgres `pg` client based helper which:
55
- provides async query functions explicitly returning multiple rows, a single row, a scalar value or nothing
66
- allows you to write SQL queries with named parameters (instead of positional parameters)
77
- handles your connections
8+
- supports transactions (since v1.1.0)
89

910
## Logging
1011

@@ -141,6 +142,41 @@ PG.query(query, ["John Doe", "[email protected]", true]);
141142

142143
**pg-client-helper** manages your database connection by utilizing connection pooling. It also supports connections to AWS RDS via IAM using AWS Signer.
143144

145+
If you want to use transactions, please use the following approach:
146+
147+
```ts
148+
import * as PG from "pg-client-helper";
149+
150+
async function myfunc() {
151+
const client: any = PG.beginTransaction(); // begins the transaction and returns a client to be used for ALL
152+
153+
try {
154+
// 1st query
155+
const $id_mytable1 = await PG.query(
156+
`INSERT INTO mytable1 (val1) VALUES 'foo' RETURNING id_mytable1`
157+
);
158+
159+
// 2nd query
160+
const $id_mytable2 = await PG.query(
161+
`INSERT INTO mytable2 (id_mytable1, val2) VALUES ($id_mytable1, 'bar')`,
162+
{ $id_mytable1 }
163+
);
164+
165+
// 3rd query
166+
await PG.query(
167+
`UPDATE mytable3 SET val3 = 'baz' WHERE id_mytable1 = $id_mytable1 AND id_mytable2 = $id_mytable2`,
168+
{ $id_mytable1, $id_mytable2 }
169+
);
170+
171+
await PG.commitTransaction(client); // commits all changes made since beginTransaction
172+
} catch (error) {
173+
if (client) {
174+
await PG.rollbackTransaction(client); // we faced an error after beginTransaction, roll back all changes since then
175+
}
176+
}
177+
}
178+
```
179+
144180
| Environment Variable | Description |
145181
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146182
| PGHOST | **(Mandatory)** The hostname of your PostgreSQL server. This could be a local hostname, IP address, or a remote server address. |

lib/pg-client-helper.ts

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
PG Client Helper
33
4-
Copyright (c) 2023, Jörg 'MK2k' Sonntag, Steffen Stolze
4+
Copyright (c) 2023-2024, Jörg 'MK2k' Sonntag, Steffen Stolze
55
66
Internet Consortium License (ISC)
77
*/
@@ -150,6 +150,34 @@ function isTrue(value: any) {
150150
return value === true || value === 1 || value?.toLowerCase() === "true";
151151
}
152152

153+
/**
154+
* Begin a transaction by creating a client from the pool and starting a transaction
155+
* @returns client - the client to use for further queries in the transaction
156+
*/
157+
export async function beginTransaction() {
158+
const client = await pool.connect();
159+
await client.query("BEGIN");
160+
return client;
161+
}
162+
163+
/**
164+
* Commit a transaction and release the client back to the pool (DO NOT use the client after calling this function!)
165+
* @param client
166+
*/
167+
export async function commitTransaction(client: any) {
168+
await client.query("COMMIT");
169+
client.release();
170+
}
171+
172+
/**
173+
* Roll back a transaction and release the client back to the pool (DO NOT use the client after calling this function!)
174+
* @param client
175+
*/
176+
export async function rollbackTransaction(client: any) {
177+
await client.query("ROLLBACK");
178+
client.release();
179+
}
180+
153181
/**
154182
* Transform query and query params from Object to Array
155183
*
@@ -216,15 +244,34 @@ export function transformQuery(
216244
return [out_query, out_parameters];
217245
}
218246

247+
/**
248+
* Query the database and return multiple rows, e.g. "SELECT * FROM mytable WHERE some_field = $some_field"
249+
* @param query the query to execute
250+
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
251+
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
252+
* @returns Array<any> - an array of rows
253+
*/
219254
export async function queryMultiple(
220255
query: string,
221-
queryParams?: Object | Array<any>
256+
queryParams?: Object | Array<any>,
257+
client?: any
222258
) {
223-
let client: any = null;
259+
let isClientCreatedHere = false;
260+
261+
if (!client) {
262+
isClientCreatedHere = true;
263+
264+
try {
265+
client = await pool.connect();
266+
} catch (error) {
267+
logger.error(`[PG] Error while creating client from pool:`, error);
268+
throw error;
269+
}
270+
}
271+
224272
let transformedQueryAndParams = null;
225-
try {
226-
client = await pool.connect();
227273

274+
try {
228275
transformedQueryAndParams = transformQuery(query, queryParams);
229276

230277
logger.log({ transformedQueryAndParams });
@@ -234,32 +281,61 @@ export async function queryMultiple(
234281
return rows;
235282
} catch (error) {
236283
logger.error(`[PG] Error in query:`, error);
237-
logger.error('[PG] Transformed query and params were:', transformedQueryAndParams);
284+
logger.error(
285+
"[PG] Transformed query and params were:",
286+
transformedQueryAndParams
287+
);
238288
throw error;
239289
} finally {
240-
if (client) {
290+
if (isClientCreatedHere && client) {
241291
client.release();
242292
}
243293
}
244294
}
245295

246-
export async function query(query: string, queryParams?: Object | Array<any>) {
247-
await queryMultiple(query, queryParams);
296+
/**
297+
* Run a query without returning any result, e.g. "INSERT INTO mytable (id, name) VALUES ($id, $name)"
298+
* @param query the query to execute
299+
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
300+
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
301+
*/
302+
export async function query(
303+
query: string,
304+
queryParams?: Object | Array<any>,
305+
client?: any
306+
) {
307+
await queryMultiple(query, queryParams, client);
248308
}
249309

310+
/**
311+
* Query the database and return a single row value, e.g. "SELECT * FROM mytable WHERE id = $id" returns the row with the given id
312+
* @param query the query to execute
313+
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
314+
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
315+
* @returns
316+
*/
250317
export async function querySingle(
251318
query: string,
252-
queryParams?: Object | Array<any>
319+
queryParams?: Object | Array<any>,
320+
client?: any
253321
) {
254-
const rows = await queryMultiple(query, queryParams);
322+
const rows = await queryMultiple(query, queryParams, client);
255323
return rows[0];
256324
}
257325

326+
/**
327+
* Query the database and return a single scalar value, e.g. "SELECT COUNT(*) FROM mytable" returns the number of rows in the table as a number
328+
* @param query the query to execute
329+
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
330+
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
331+
* @returns
332+
*/
258333
export async function queryScalar(
259334
query: string,
260-
queryParams?: Object | Array<any>
335+
queryParams?: Object | Array<any>,
336+
client?: any
261337
) {
262-
const row = await querySingle(query, queryParams);
338+
const row = await querySingle(query, queryParams, client);
263339
if (!row) {
264340
return null;
265341
}

0 commit comments

Comments
 (0)