From e2c28f87354462d6dee00fa8a09bec29e7088e08 Mon Sep 17 00:00:00 2001 From: sol Date: Sun, 29 Dec 2024 00:07:30 +0200 Subject: [PATCH] sql: sqlite > mariadb --- index.ts | 2 +- package.json | 2 +- src/sqlFunctions.ts | 261 +++++++++++++++++++++++++++++++++----------- 3 files changed, 201 insertions(+), 64 deletions(-) diff --git a/index.ts b/index.ts index 67ab43d..6575f99 100644 --- a/index.ts +++ b/index.ts @@ -28,7 +28,7 @@ const bazaarPrice = { }; async function initialize() { - await SqlSystem.InitTable(); + await SqlSystem.InitDB() const matches = process.env.WEBHOOK_URL.match(webhookRegex); if (!matches) return console.log(`[Main thread] Couldn't parse Webhook URL`); const webhook = new WebhookClient({ id: matches[1], token: matches[2] }); diff --git a/package.json b/package.json index a417188..208b414 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,11 @@ "copy-paste": "^1.5.3", "discord.js": "^14.16.3", "express": "^4.21.2", + "mariadb": "^3.4.0", "node-notifier": "^10.0.1", "open": "^8.4.2", "prismarine-nbt": "^2.7.0", "socket.io": "^4.8.1", - "sqlite3": "^5.1.7", "toastify-js": "^1.12.0", "ts-node": "^10.9.2", "typescript": "^5.7.2" diff --git a/src/sqlFunctions.ts b/src/sqlFunctions.ts index b5f6762..e72480c 100644 --- a/src/sqlFunctions.ts +++ b/src/sqlFunctions.ts @@ -1,72 +1,209 @@ -import sqlite3 from 'sqlite3'; +import mariadb from 'mariadb'; +import { loadConfig } from './configLoader'; +const config = loadConfig(); -//TODO -// MUTEX functions for adding/removing/upsert -// basic read function by id -// complex read function by value range class SqlSystem { - private static db: sqlite3.Database; - public static async InitTable() { - const db = new sqlite3.Database('bot_data'); - try{ - await this.runQuery('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)') - console.log('Table created successfully.'); + private static pool: mariadb.Pool = mariadb.createPool({ database: process.env.DATABASE, host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, connectionLimit: config.data.worker_count ?? 10 }) - // Insert data - await this.runQuery('INSERT INTO users (name) VALUES (?)', ['Alice']); - console.log('Data inserted successfully.'); - - // Retrieve a single row - const row = await this.getQuery('SELECT * FROM users WHERE name = ?', ['Alice']); - console.log('Retrieved row:', row); - - // Retrieve all rows - const rows = await this.allQuery('SELECT * FROM users'); - console.log('All rows:', rows); - } catch (err: any) { - console.error('Database error:', err.message); - } finally { - db.close(); + public static async InitDB() { + let conn: mariadb.PoolConnection | undefined; + try { + conn = await this.pool.getConnection(); + //Setup tables for auctions and lifetimes + await conn.query(` + CREATE TABLE if NOT EXISTS auctions ( + id INT AUTO_INCREMENT PRIMARY KEY, + auctionid VARCHAR(255) NOT NULL, + lbin DECIMAL(65,5) SIGNED, + UNIQUE INDEX 'auctionid' ('auctionid') + ); + CREATE TABLE if NOT EXISTS lifetimes (id INT PRIMARY KEY, insertedon DATETIME(2)); + `) + //Setup lifetime Trigger + await conn.query(` + DELIMITER $$ + CREATE TRIGGER IF NOT EXISTS insertLifetime + AFTER INSERT ON auctions + FOR EACH ROW + BEGIN + INSERT INTO lifetimes (id, insertedon) + VALUES (NEW.ID, NOW()); + END$$ + DELIMITER ; + DELIMITER $$ + CREATE TRIGGER IF NOT EXISTS updateLifetime + AFTER UPDATE ON auctions + FOR EACH ROW + BEGIN + UPDATE lifetimes SET insertedon = NOW() WHERE id=NEW.ID; + END$$ + DELIMITER ; + DELIMITER $$ + CREATE TRIGGER IF NOT EXISTS removeLifetime + BEFORE DELETE ON auctions + FOR EACH ROW + BEGIN + DELETE FROM lifetimes WHERE id=OLD.ID; + END$$ + DELIMITER ; + `) + //Setup Table sanitation + await conn.query(` + DELIMITER $$ + CREATE PROCEDURE IF NOT EXISTS CleanupOldEntries() + BEGIN + DECLARE done INT DEFAULT 0; + DECLARE temp_id INT; + DECLARE cur CURSOR FOR + SELECT id + FROM lifetimes + WHERE TIMESTAMPDIFF(HOUR, insertedon, NOW()) >= 6; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; + OPEN cur; + read_loop: LOOP + FETCH cur INTO temp_id; + IF done THEN + LEAVE read_loop; + END IF; + DELETE FROM auctions WHERE id = temp_id; + END LOOP; + CLOSE cur; + END$$ + DELIMITER ; + SET GLOBAL event_scheduler = ON; + CREATE EVENT IF NOT EXISTS CleanupOldEntriesEvent + ON SCHEDULE EVERY 2 MINUTE + DO + CALL CleanupOldEntries(); + `) + } + catch (error) { + console.error("InitDB Error: ", error); + throw error; + } + finally { + if (conn) conn.release(); + } } -} - public static runQuery(query: string, params: string[] = []) { - return new Promise((resolve,reject) => { - this.db.run(query, params, function (err: Error | null | undefined) { - if(err){ - reject(err); + //INSERT ELEMENT IN AUCTIONS TABLE OR UPDATE IF IT ALREADY EXISTS + public static async Upsert(auctionid: string, lbin: number) { + let conn: mariadb.PoolConnection | undefined; + try { + conn = await this.pool.getConnection(); + await conn.beginTransaction(); + await conn.query(` + INSERT INTO auctions (auctionid, LBin) + VALUES (?,?) + ON DUPLICATE KEY UPDATE + LBin = VALUES(LBin); + `, [auctionid, lbin]); + await conn.commit(); + } + catch (error) { + console.error("InitDB Error: ", error); + if(conn) await conn.rollback(); + throw error; + } + finally { + if (conn) conn.release(); + } + } +//REMOVE ELEMENT IN AUCTIONS TABLE + public static async Remove(auctionId: string) { + let conn: mariadb.PoolConnection | undefined; + try { + conn = await this.pool.getConnection(); + await conn.beginTransaction(); + await conn.query(` + DELETE FROM auctions WHERE auctionid = ? + `,[auctionId]); + await conn.commit(); + } + catch (error) { + console.error("InitDB Error: ", error); + if(conn) await conn.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: mariadb.PoolConnection | undefined; + let result; + try{ + conn = await this.pool.getConnection(); + 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 match collection to singlet'); + 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[0].result) } - else{ - resolve(this); - } - }); - }); -} - public static getQuery(query: string, params: string[] = []) { - return new Promise((resolve, reject) => { - this.db.get(query, params, (err: Error | null | undefined, row: sqlite3.RunResult) => { - if (err) { - reject(err); - } else { - resolve(row); - } - }); - }); -} - public static allQuery(query: string, params: string[] = []) { - return new Promise((resolve, reject) => { - this.db.all(query, params, (err: Error | null | undefined, rows: sqlite3.RunResult) => { - if (err) { - reject(err); - } else { - resolve(rows); - } - }); - }); -} + } + 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:mariadb.PoolConnection|undefined; + try{ + conn = await this.pool.getConnection(); + await conn.beginTransaction(); + //CODE HERE// + + await conn.commit(); + } + catch(error) + { + console.error("message_before_error: ", error); + if(conn) await conn.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 -} \ No newline at end of file +}