import mysql from 'mysql2/promise' import { loadP42Secrets } from './secretsLoader.js' export class MySQLClient { constructor(db, kal = 0) { this.db = db this.lastInsertedId = -1 this.kalIntervalId = null if(kal > 0) { this.kalIntervalId = setInterval(() => { this.db.query('SELECT 1') }, 1000 * kal) } } static #assertValidDbName(name) { if(typeof(name) !== 'string' || !/^[A-Za-z0-9_]+$/.test(name)) { throw(new Error(`Invalid MySQL database name: ${name}`)) } } static resolveDatabases(config = {}) { loadP42Secrets() const guiDatabase = process.env.mysql_guiDb || config.guiDatabase || config.database || 'p42GUI' const simDatabase = process.env.mysql_simDb || config.simDatabase || 'p42SIM' MySQLClient.#assertValidDbName(guiDatabase) MySQLClient.#assertValidDbName(simDatabase) return({ guiDatabase, simDatabase }) } static resolveCredentials(config = {}) { loadP42Secrets() const user = process.env.mysql_user const password = process.env.mysql_pass if(!user || !password) { throw(new Error('Missing MySQL credentials: set mysql_user and mysql_pass in environment')) } const { guiDatabase } = MySQLClient.resolveDatabases(config) return({ socketPath: config.socketPath, host: config.host, port: config.port, user, password, database: guiDatabase, waitForConnections: true, connectionLimit: config.connectionLimit ?? 5, queueLimit: 0, }) } static async createPool(config) { return(await mysql.createPool(MySQLClient.resolveCredentials(config))) } static async poolExecute(pool, query, values = []) { const [rows] = await pool.execute(query, values) return(rows) } async close() { if(this.db) await this.db.end() if(this.kalIntervalId) clearInterval(this.kalIntervalId) this.db = null } escape(x) { return(mysql.escape(x)) } async execute(query, values = []) { if(!this.db) return([]) if(query.search(RegExp('(^|;) *(INSERT|REPLACE) +INTO', 'i')) > -1) this.lastInsertedId = -1 const [result] = await this.db.execute(query, values) if('insertId' in result) { this.lastInsertedId = result.insertId } return(result) } async InsertObjectsStatement(table, rows) { this.lastInsertedId = -1 if((!this.db) || (rows.length == 0)) return([]) const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ') const valuesArr = [] for(const row of rows) { valuesArr.push(Object.values(row)) } const query = `INSERT INTO ${table} (${keys}) VALUES ?` const [result] = await this.db.query(query, [valuesArr]) if('insertId' in result) { this.lastInsertedId = result.insertId } return(result) } async ReplaceObjectsStatement(table, rows) { this.lastInsertedId = -1 if((!this.db) || (rows.length == 0)) return([]) const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ') const valuesArr = [] for(const row of rows) { valuesArr.push(Object.values(row)) } const query = `REPLACE INTO ${table} (${keys}) VALUES ?` const [result] = await this.db.query(query, [valuesArr]) if('insertId' in result) { this.lastInsertedId = result.insertId } return(result) } }