Skip to content

Commit

Permalink
add support for transcations
Browse files Browse the repository at this point in the history
  • Loading branch information
theMK2k committed Apr 8, 2024
1 parent 9f03ecb commit 4db8114
Show file tree
Hide file tree
Showing 4 changed files with 2,112 additions and 3,089 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A Postgres `pg` client based helper which:
- provides async query functions explicitly returning multiple rows, a single row, a scalar value or nothing
- allows you to write SQL queries with named parameters (instead of positional parameters)
- handles your connections
- supports transactions (since v1.1.0)

## Logging

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

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

If you want to use transactions, please use the following approach:

```ts
import * as PG from "pg-client-helper";

async function myfunc() {
const client: any = PG.beginTransaction(); // begins the transaction and returns a client to be used for ALL

try {
// 1st query
const $id_mytable1 = await PG.query(
`INSERT INTO mytable1 (val1) VALUES 'foo' RETURNING id_mytable1`
);

// 2nd query
const $id_mytable2 = await PG.query(
`INSERT INTO mytable2 (id_mytable1, val2) VALUES ($id_mytable1, 'bar')`,
{ $id_mytable1 }
);

// 3rd query
await PG.query(
`UPDATE mytable3 SET val3 = 'baz' WHERE id_mytable1 = $id_mytable1 AND id_mytable2 = $id_mytable2`,
{ $id_mytable1, $id_mytable2 }
);

await PG.commitTransaction(client); // commits all changes made since beginTransaction
} catch (error) {
if (client) {
await PG.rollbackTransaction(client); // we faced an error after beginTransaction, roll back all changes since then
}
}
}
```

| Environment Variable | Description |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| PGHOST | **(Mandatory)** The hostname of your PostgreSQL server. This could be a local hostname, IP address, or a remote server address. |
Expand Down
102 changes: 89 additions & 13 deletions lib/pg-client-helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
PG Client Helper
Copyright (c) 2023, Jörg 'MK2k' Sonntag, Steffen Stolze
Copyright (c) 2023-2024, Jörg 'MK2k' Sonntag, Steffen Stolze
Internet Consortium License (ISC)
*/
Expand Down Expand Up @@ -150,6 +150,34 @@ function isTrue(value: any) {
return value === true || value === 1 || value?.toLowerCase() === "true";
}

/**
* Begin a transaction by creating a client from the pool and starting a transaction
* @returns client - the client to use for further queries in the transaction
*/
export async function beginTransaction() {
const client = await pool.connect();
await client.query("BEGIN");
return client;
}

/**
* Commit a transaction and release the client back to the pool (DO NOT use the client after calling this function!)
* @param client
*/
export async function commitTransaction(client: any) {
await client.query("COMMIT");
client.release();
}

/**
* Roll back a transaction and release the client back to the pool (DO NOT use the client after calling this function!)
* @param client
*/
export async function rollbackTransaction(client: any) {
await client.query("ROLLBACK");
client.release();
}

/**
* Transform query and query params from Object to Array
*
Expand Down Expand Up @@ -216,15 +244,34 @@ export function transformQuery(
return [out_query, out_parameters];
}

/**
* Query the database and return multiple rows, e.g. "SELECT * FROM mytable WHERE some_field = $some_field"
* @param query the query to execute
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
* @returns Array<any> - an array of rows
*/
export async function queryMultiple(
query: string,
queryParams?: Object | Array<any>
queryParams?: Object | Array<any>,
client?: any
) {
let client: any = null;
let isClientCreatedHere = false;

if (!client) {
isClientCreatedHere = true;

try {
client = await pool.connect();
} catch (error) {
logger.error(`[PG] Error while creating client from pool:`, error);
throw error;
}
}

let transformedQueryAndParams = null;
try {
client = await pool.connect();

try {
transformedQueryAndParams = transformQuery(query, queryParams);

logger.log({ transformedQueryAndParams });
Expand All @@ -234,32 +281,61 @@ export async function queryMultiple(
return rows;
} catch (error) {
logger.error(`[PG] Error in query:`, error);
logger.error('[PG] Transformed query and params were:', transformedQueryAndParams);
logger.error(
"[PG] Transformed query and params were:",
transformedQueryAndParams
);
throw error;
} finally {
if (client) {
if (isClientCreatedHere && client) {
client.release();
}
}
}

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

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

/**
* 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
* @param query the query to execute
* @param queryParams (optional) - pass an object with named parameters to replace in the query, prefix the named parameter with a dollar sign '$'
* @param client (optional) - pass an existing client (e.g. during a transaction) to use it instead of creating a new one
* @returns
*/
export async function queryScalar(
query: string,
queryParams?: Object | Array<any>
queryParams?: Object | Array<any>,
client?: any
) {
const row = await querySingle(query, queryParams);
const row = await querySingle(query, queryParams, client);
if (!row) {
return null;
}
Expand Down
Loading

0 comments on commit 4db8114

Please sign in to comment.