From 3e3903a60da23f8b9cfd5c92cd1530ccda351469 Mon Sep 17 00:00:00 2001 From: Rudo Kemper Date: Fri, 6 Dec 2024 12:30:11 -0500 Subject: [PATCH 1/2] Implement connection pooling --- server/database/dbConnection.ts | 54 ++++++++------- server/database/dbOperations.ts | 112 +++++++++----------------------- server/types.ts | 4 -- 3 files changed, 56 insertions(+), 114 deletions(-) diff --git a/server/database/dbConnection.ts b/server/database/dbConnection.ts index 44f349a..2a2eef8 100644 --- a/server/database/dbConnection.ts +++ b/server/database/dbConnection.ts @@ -1,38 +1,36 @@ -import pkg from "pg"; -const { Client } = pkg; +import pkg, { Pool as PoolType } from "pg"; +const { Pool } = pkg; -import { type DatabaseConnection } from "../types"; import { getConfig } from "./dbConfig"; -let db: DatabaseConnection | null = null; +let db: PoolType | null = null; -export const setupDatabaseConnection = - async (): Promise => { - const { database, dbHost, dbUser, dbPassword, dbPort, dbSsl } = getConfig(); +export const setupDatabaseConnection = async (): Promise => { + const { database, dbHost, dbUser, dbPassword, dbPort, dbSsl } = getConfig(); - const dbConnection = { - database: database, - user: dbUser, - host: dbHost, - password: dbPassword, - port: parseInt(dbPort, 10), - ssl: dbSsl === true ? { rejectUnauthorized: false } : false, - }; - db = new Client(dbConnection); + const dbConnection = { + database: database, + user: dbUser, + host: dbHost, + password: dbPassword, + port: parseInt(dbPort, 10), + ssl: dbSsl === true ? { rejectUnauthorized: false } : false, + }; + db = new Pool(dbConnection); - db.connect() - .then(() => { - console.log("Connected to the PostgreSQL database"); - }) - .catch((error: Error) => { - db = null; - console.error("Error connecting to the PostgreSQL database:", error); - }); + db.on("connect", () => { + console.log("Connected to the PostgreSQL database"); + }); - return db; - }; + db.on("error", (error: Error) => { + db = null; + console.error("Error connecting to the PostgreSQL database:", error); + }); + + return db; +}; -export const getDatabaseConnection = async (): Promise => { +export const getDatabaseConnection = async (): Promise => { if (db) { await ensurePostgresConnection(db); } else { @@ -48,7 +46,7 @@ export const refreshDatabaseConnection = async (): Promise => { db = await setupDatabaseConnection(); }; -async function ensurePostgresConnection(db: DatabaseConnection): Promise { +async function ensurePostgresConnection(db: PoolType): Promise { try { await db.query("SELECT 1"); // Simple query to check connection } catch (error) { diff --git a/server/database/dbOperations.ts b/server/database/dbOperations.ts index fc4991c..eda9bcf 100644 --- a/server/database/dbOperations.ts +++ b/server/database/dbOperations.ts @@ -1,26 +1,18 @@ -import { Client } from "pg"; +import { Pool } from "pg"; import { type MapRequest } from "../types"; -const checkTableExists = ( - db: Client, +const checkTableExists = async ( + db: Pool, table: string | undefined, ): Promise => { - return new Promise((resolve, reject) => { - const query = `SELECT to_regclass('${table}')`; - db.query<{ to_regclass: string | null }>( - query, - [], - (err: Error, result) => { - if (err) reject(err); - resolve(result.rows[0].to_regclass !== null); - }, - ); - }); + const query = `SELECT to_regclass('${table}')`; + const result = await db.query<{ to_regclass: string | null }>(query); + return result.rows[0].to_regclass !== null; }; const createMapRequestTable = async ( - db: Client, + db: Pool, table: string | undefined, ): Promise => { console.log(`Table ${table} does not exist. Creating...`); @@ -52,17 +44,12 @@ const createMapRequestTable = async ( work_ended TIMESTAMP(6) ) `; - return new Promise((resolve, reject) => { - db.query(query, (err: Error) => { - if (err) reject(err); - console.log(`Table ${table} created successfully`); - resolve(); - }); - }); + await db.query(query); + console.log(`Table ${table} created successfully`); }; const fetchDataFromTable = async ( - db: Client, + db: Pool, table: string | undefined, limit: number, cursor: number | null, @@ -77,21 +64,15 @@ const fetchDataFromTable = async ( query = `SELECT * FROM ${table} ORDER BY id DESC LIMIT $1`; } - return new Promise((resolve, reject) => { - db.query(query, values, (err: Error, result: { rows: MapRequest[] }) => { - if (err) { - reject(err); - } else if (!result || !result.rows) { - reject(new Error("No result returned from query.")); - } else { - resolve(result.rows); - } - }); - }); + const result = await db.query(query, values); + if (!result || !result.rows) { + throw new Error("No result returned from query."); + } + return result.rows; }; export const fetchData = async ( - db: Client, + db: Pool, table: string | undefined, limit: number, cursor: number | null, @@ -106,7 +87,7 @@ export const fetchData = async ( }; export const insertDataIntoTable = async ( - db: Client, + db: Pool, table: string | undefined, data: MapRequest, ): Promise => { @@ -119,26 +100,17 @@ export const insertDataIntoTable = async ( .map((_, i) => `$${i + 1}`) .join(", "); const values = Object.values(data); - // Return id so it can be used if needed for error handling const query = `INSERT INTO ${table} (${columns}) VALUES (${placeholders}) RETURNING id`; - return new Promise((resolve, reject) => { - db.query( - query, - values, - (err: Error, result: { rows: { id: number }[] }) => { - if (err) reject(err); - if (result.rows.length > 0) { - resolve(result.rows[0].id); - } else { - reject(new Error("No rows returned after insert.")); - } - }, - ); - }); + const result = await db.query(query, values); + if (result.rows.length > 0) { + return result.rows[0].id; + } else { + throw new Error("No rows returned after insert."); + } }; export const handleDeleteRequest = async ( - db: Client, + db: Pool, table: string | undefined, requestId: number | void | null, ): Promise => { @@ -172,7 +144,7 @@ export const handleDeleteRequest = async ( }; export async function updateDatabaseMapRequest( - db: Client, + db: Pool, tableName: string, id: number | void | null, data: Partial, @@ -193,23 +165,12 @@ export async function updateDatabaseMapRequest( WHERE id = $${values.length} `; - return new Promise((resolve, reject) => { - db.query(query, values, (err: Error) => { - if (err) { - console.error( - `Error updating record ${id} in table ${tableName}: ${err.message}`, - ); - reject(err); - } else { - console.log(`Record ${id} in table ${tableName} updated.`); - resolve(); - } - }); - }); + await db.query(query, values); + console.log(`Record ${id} in table ${tableName} updated.`); } export async function updateDatabaseWithError( - db: Client, + db: Pool, tableName: string, id: number | void | null, errorMessage: string, @@ -224,19 +185,6 @@ export async function updateDatabaseWithError( WHERE id = $2 `; - return new Promise((resolve, reject) => { - db.query(query, [errorMessage, id], (err: Error) => { - if (err) { - console.error( - `Error updating record ${id} in table ${tableName}: ${err.message}`, - ); - reject(err); - } else { - console.log( - `Record ${id} in table ${tableName} updated with error message.`, - ); - resolve(); - } - }); - }); + await db.query(query, [errorMessage, id]); + console.log(`Record ${id} in table ${tableName} updated with error message.`); } diff --git a/server/types.ts b/server/types.ts index 78856f0..0f46258 100644 --- a/server/types.ts +++ b/server/types.ts @@ -1,7 +1,3 @@ -import { Client } from "pg"; - -export type DatabaseConnection = typeof Client.prototype; - export type MapStyleKey = | "bing" | "google" From 8766892ddde956be7bb5786f79dc22b23533208d Mon Sep 17 00:00:00 2001 From: Rudo Kemper Date: Fri, 6 Dec 2024 14:23:51 -0500 Subject: [PATCH 2/2] Wrap db.query in try/catch block --- server/database/dbOperations.ts | 68 ++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/server/database/dbOperations.ts b/server/database/dbOperations.ts index eda9bcf..d57773a 100644 --- a/server/database/dbOperations.ts +++ b/server/database/dbOperations.ts @@ -7,7 +7,14 @@ const checkTableExists = async ( table: string | undefined, ): Promise => { const query = `SELECT to_regclass('${table}')`; - const result = await db.query<{ to_regclass: string | null }>(query); + let result; + try { + result = await db.query<{ to_regclass: string | null }>(query); + } catch (error) { + throw new Error( + `Failed to check if table exists: ${(error as Error).message}`, + ); + } return result.rows[0].to_regclass !== null; }; @@ -44,8 +51,14 @@ const createMapRequestTable = async ( work_ended TIMESTAMP(6) ) `; - await db.query(query); - console.log(`Table ${table} created successfully`); + try { + await db.query(query); + console.log(`Table ${table} created successfully`); + } catch (error) { + throw new Error( + `Failed to create table ${table}: ${(error as Error).message}`, + ); + } }; const fetchDataFromTable = async ( @@ -64,7 +77,12 @@ const fetchDataFromTable = async ( query = `SELECT * FROM ${table} ORDER BY id DESC LIMIT $1`; } - const result = await db.query(query, values); + let result; + try { + result = await db.query(query, values); + } catch (error) { + throw new Error(`Failed to fetch data from table ${table}: ${error}`); + } if (!result || !result.rows) { throw new Error("No result returned from query."); } @@ -100,8 +118,16 @@ export const insertDataIntoTable = async ( .map((_, i) => `$${i + 1}`) .join(", "); const values = Object.values(data); + // Return id so it can be used if needed for error handling const query = `INSERT INTO ${table} (${columns}) VALUES (${placeholders}) RETURNING id`; - const result = await db.query(query, values); + let result; + try { + result = await db.query(query, values); + } catch (error) { + throw new Error( + `Failed to insert data into table ${table}: ${(error as Error).message}`, + ); + } if (result.rows.length > 0) { return result.rows[0].id; } else { @@ -123,13 +149,24 @@ export const handleDeleteRequest = async ( } const query = `SELECT file_location, filename FROM ${table} WHERE id = $1`; - const result = await db.query(query, [requestId]); + let result; + try { + result = await db.query(query, [requestId]); + } catch (error) { + throw new Error( + `Failed to fetch file location and filename: ${(error as Error).message}`, + ); + } const { file_location, filename } = result.rows[0]; if (!file_location || !filename) { console.log("File location or filename is NULL. Deleting row..."); const deleteQuery = `DELETE FROM ${table} WHERE id = $1`; - await db.query(deleteQuery, [requestId]); + try { + await db.query(deleteQuery, [requestId]); + } catch (error) { + throw new Error(`Failed to delete row: ${error}`); + } return false; } else { if (!table) { @@ -165,7 +202,13 @@ export async function updateDatabaseMapRequest( WHERE id = $${values.length} `; - await db.query(query, values); + try { + await db.query(query, values); + } catch (error) { + throw new Error( + `Error updating record ${id} in table ${tableName}: ${(error as Error).message}`, + ); + } console.log(`Record ${id} in table ${tableName} updated.`); } @@ -184,7 +227,12 @@ export async function updateDatabaseWithError( SET status = 'FAILED', error_message = $1 WHERE id = $2 `; - - await db.query(query, [errorMessage, id]); + try { + await db.query(query, [errorMessage, id]); + } catch (error) { + throw new Error( + `Error updating record ${id} in table ${tableName}: ${(error as Error).message}`, + ); + } console.log(`Record ${id} in table ${tableName} updated with error message.`); }