import { Pool, PoolClient } from 'pg'; import { loadConfig } from './configLoader'; const config = loadConfig(); class SqlSystem { private static pool: Pool = new Pool({ database: process.env.DATABASE, host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, max: config.data.worker_count ?? 10 }) public static async InitDB() { let conn: PoolClient | undefined; try { conn = await this.pool.connect(); //Setup tables for auctions and lifetimes await conn.query(` CREATE TABLE if NOT EXISTS auctions ( id UUID PRIMARY KEY, auctionid VARCHAR(255) NOT NULL, lbin DECIMAL(65,5), UNIQUE (auctionid) ); CREATE TABLE if NOT EXISTS lifetimes (id UUID PRIMARY KEY, insertedon TIMESTAMP(2)); `); //Setup lifetime Trigger await conn.query(` -- Insert trigger function CREATE OR REPLACE FUNCTION public.fn_insert_lifetime() RETURNS TRIGGER AS $$ BEGIN INSERT INTO lifetimes (id, insertedon) VALUES (NEW.id, NOW()); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Update trigger function CREATE OR REPLACE FUNCTION public.fn_update_lifetime() RETURNS TRIGGER AS $$ BEGIN UPDATE lifetimes SET insertedon = NOW() WHERE id = NEW.id; RETURN NEW; END; $$ LANGUAGE plpgsql; -- Remove trigger function CREATE OR REPLACE FUNCTION public.fn_remove_lifetime() RETURNS TRIGGER AS $$ BEGIN DELETE FROM lifetimes WHERE id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; `); await conn.query(` -- Insert trigger DROP TRIGGER IF EXISTS insert_lifetime ON auctions; CREATE TRIGGER insert_lifetime AFTER INSERT ON auctions FOR EACH ROW EXECUTE PROCEDURE public.fn_insert_lifetime(); -- Update trigger DROP TRIGGER IF EXISTS update_lifetime ON auctions; CREATE TRIGGER update_lifetime AFTER UPDATE ON auctions FOR EACH ROW EXECUTE PROCEDURE public.fn_update_lifetime(); -- Remove trigger DROP TRIGGER IF EXISTS remove_lifetime ON auctions; CREATE TRIGGER remove_lifetime BEFORE DELETE ON auctions FOR EACH ROW EXECUTE PROCEDURE public.fn_remove_lifetime(); `); //Setup Table sanitation await conn.query(` CREATE OR REPLACE FUNCTION public.cleanup_old_entries() RETURNS void LANGUAGE plpgsql AS $$ DECLARE temp_id INT; BEGIN FOR temp_id IN SELECT id FROM lifetimes WHERE EXTRACT(EPOCH FROM (NOW() - insertedon))/3600 >= 6 -- 6 hours LOOP -- Removes matching entries from auctions DELETE FROM auctions WHERE auctions.id = temp_id; END LOOP; RETURN; END; $$; `); } catch (error) { console.error("InitDB Error: ", error); throw error; } finally { if (conn) conn.release(); } } //INSERT ELEMENT IN AUCTIONS TABLE OR UPDATE IF IT ALREADY EXISTS public static async Upsert(auctionid: string|string[], lbin: number|number[]) { let conn: PoolClient | undefined; try { conn = await this.pool.connect(); await conn.query('BEGIN'); if(Array.isArray(auctionid) && Array.isArray(lbin)) if(auctionid.length === lbin.length) { await conn.query(` INSERT INTO auctions (auctionid, LBin) VALUES (?,?) ON DUPLICATE KEY UPDATE LBin = VALUES(LBin); `,[await this.UnifiedArray(auctionid,lbin)]); } else if(Array.isArray(auctionid) || Array.isArray(lbin)) throw Error(`Upsert SQL Function error - cannot unify collection ${Array.isArray(auctionid) ? auctionid : lbin} to singlet ${!Array.isArray(auctionid) ? auctionid : lbin}`); else { await conn.query(` INSERT INTO auctions (auctionid, LBin) VALUES (?,?) ON DUPLICATE KEY UPDATE LBin = VALUES(LBin); `, [auctionid, lbin]); } await conn.query('COMMIT'); } catch(error) { console.error("message_before_error: ", error); if (conn) await conn.query('ROLLBACK'); throw error; } finally { if (conn) conn.release(); } } //REMOVE ELEMENT IN AUCTIONS TABLE public static async Remove(auctionid: string|string[]) { let conn: PoolClient | undefined; try { conn = await this.pool.connect(); await conn.query('BEGIN'); await conn.query(` DELETE FROM auctions WHERE auctionid = ? `,[auctionid]); await conn.query('COMMIT'); } catch (error) { console.error("InitDB Error: ", error); if (conn) await conn.query('ROLLBACK'); throw error; } finally { if (conn) conn.release(); } } public static async Query(query:string,use_transaction:boolean = true) { let conn: PoolClient | undefined; try{ conn = await this.pool.connect() if (use_transaction) await conn.query('BEGIN'); await conn.query(query); if (use_transaction) await conn.query('COMMIT'); } catch(error) { console.error("InitDB Error: ", error); if (use_transaction && conn) await conn.query('ROLLBACK'); throw error; } finally { if(conn) conn.release(); } } //MATCH PROVIDED ELEMENTS IN AUCTIONS TABLE - returns true/false public static async Match(auctionid:string|string[],lbin:number|number[]): Promise { let conn: PoolClient | undefined; let result; try{ conn = await this.pool.connect(); if(Array.isArray(auctionid) && Array.isArray(lbin)) if(auctionid.length === lbin.length) { result = await conn.query(` CREATE TEMPORARY TABLE IF NOT EXISTS TEMP ( auctionID VARCHAR(255) PRIMARY KEY, lbin DECIMAL(65,5) ); INSERT INTO TEMP (auctionID, lbin) VALUES ? SELECT tmp.* FROM TEMP AS tmp WHERE NOT EXISTS ( SELECT 1 FROM auctions AS auc WHERE auc.auctionID = tmp.auctionID AND auc.lbin = tmp.lbin); `,[await this.UnifiedArray(auctionid,lbin)]); return result; } else if(Array.isArray(auctionid) || Array.isArray(lbin)) throw Error(`Match SQL Function error - cannot unify collection ${Array.isArray(auctionid) ? auctionid : lbin} to singlet ${!Array.isArray(auctionid) ? auctionid : lbin}`); else { result = await conn.query(` SELECT CASE WHEN COUNT(*) > 0 THEN true ELSE false END AS user_exists FROM auctions WHERE auctionID = ?; `,[auctionid]); return Boolean(result.rows.length); } } catch(error) { console.error("message_before_error: ", error); throw error; } finally { if (conn) conn.release(); } } //EXAMPLE BLOCK OF CODE FOR ADDING DATABASE FUNCTIONS /* public static async example_name() { let conn:PoolClient|undefined; try{ conn = await this.pool.connect(); await conn.query('BEGIN'); //CODE HERE// await conn.query('COMMIT'); } catch(error) { console.error("message_before_error: ", error); if(conn) await conn.query('ROLLBACK'); throw error; } finally { if(conn) conn.release(); } */ private static async UnifiedArray(auctionid: string[], lbin: number[]): Promise<{ x: string[], y: number[] }> { return { x: [...auctionid], y: [...lbin] }; } } export { SqlSystem }