UNPKG

inceptum

Version:

hipages take on the foundational library for enterprise-grade apps written in NodeJS

240 lines 8.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const mysql = require("mysql"); const DBTransaction_1 = require("../db/DBTransaction"); const TransactionManager_1 = require("../transaction/TransactionManager"); const PromiseUtil_1 = require("../util/PromiseUtil"); const LogManager_1 = require("../log/LogManager"); const Metrics_1 = require("../metrics/Metrics"); const DBClient_1 = require("../db/DBClient"); const log = LogManager_1.LogManager.getLogger(__filename); function getConnectionPromise(connectionPool) { return new Promise((resolve, reject) => { connectionPool.getConnection((err, connection) => { if (err) { reject(err); } else { resolve(connection); } }); }); } class MysqlTransaction extends DBTransaction_1.DBTransaction { /** * * @param {MysqlClient} myslqClient * @param transaction */ constructor(mysqlClient, transaction) { super(transaction); this.mysqlClient = mysqlClient; } // tslint:disable-next-line:prefer-function-over-method runQueryOnPool(sql, bindsArr) { log.debug(`sql: ${sql} ${(bindsArr && (bindsArr.length > 0)) ? `| ${bindsArr}` : ''}`); if (!Array.isArray(bindsArr)) { bindsArr = []; } return new Promise((resolve, reject) => this.connection.query(sql, bindsArr, (err, rows) => { if (err) { log.error({ err }, `SQL error for ${sql}`); return reject(err); } return resolve(rows); })); } runQueryPrivate(sql, bindsArr) { return PromiseUtil_1.PromiseUtil.try(() => { // tslint:disable-next-line:no-invalid-this if (!this.connection) { // tslint:disable-next-line:no-invalid-this return getConnectionPromise(this.mysqlClient.getConnectionPoolForReadonly(this.transaction.isReadonly())); } // tslint:disable-next-line:no-invalid-this return this.connection; }) .then((connection) => { this.connection = connection; return connection; }) .then((connection) => this.runQueryOnPool(`/* Transaction Id ${this.transaction.id} */ ${sql}`, bindsArr)); } runQueryAssocPrivate(sql, bindsObj) { if (sql.indexOf('::') < 0 || !bindsObj) { // tslint:disable-next-line:no-invalid-this return this.runQueryPrivate.call(this, sql, []); } sql.replace(/::(\w)+::/g, (substr, key) => { if (bindsObj.hasOwnProperty(key)) { return bindsObj[key]; } return substr; }); } async doTransactionEnd() { this.connection.release(); } } exports.MysqlTransaction = MysqlTransaction; class MetricsAwareConnectionPoolWrapper { constructor(instance, name) { this.instance = instance; this.active = 0; this.numConnections = 0; this.enqueueTimes = []; this.setupPool(); this.durationHistogram = Metrics_1.MetricsService.histogram(`db_pool_${name}`); } setupPool() { const self = this; if (this.instance.on) { this.instance.on('acquire', () => { self.active++; if (this.enqueueTimes.length > 0) { const start = this.enqueueTimes.shift(); self.registerWaitTime(Date.now() - start); } }); this.instance.on('connection', () => { self.numConnections++; }); this.instance.on('enqueue', () => { this.enqueueTimes.push(Date.now()); }); this.instance.on('release', () => { this.active--; }); } } registerWaitTime(duration) { this.durationHistogram.observe(duration); } end() { return this.instance.end(); } getConnection(cb) { this.instance.getConnection((err, connection) => { cb(err, connection); }); } } /** * A MySQL client you can use to execute queries against MySQL */ class MysqlClient extends DBClient_1.DBClient { constructor() { super(); this.configuration = {}; this.name = 'NotSet'; this.masterPool = null; this.slavePool = null; this.enable57Mode = false; this.connectionPoolCreator = (config) => new MetricsAwareConnectionPoolWrapper(mysql.createPool(config), this.name); } // configuration and name are two properties set by MysqlConfigManager initialise() { this.enable57Mode = this.configuration.enable57Mode || false; if (this.configuration.master) { this.masterPool = this.connectionPoolCreator(this.getFullPoolConfig(this.configuration.master)); } if (this.configuration.slave) { this.slavePool = this.connectionPoolCreator(this.getFullPoolConfig(this.configuration.slave)); } if (!this.masterPool && !this.slavePool) { throw new Error(`MysqlClient ${this.name} has no connections configured for either master or slave`); } } /** * Runs a function in a transaction. The function must receive one parameter that will be of class * {MysqlTransaction} and that you need to use to run all queries in this transaction * * @param {boolean} readonly Whether the transaction needs to be readonly or not * @param {Function} func A function that returns a promise that will execute all the queries wanted in this transaction * @returns {Promise} A promise that will execute the whole transaction */ async runInTransaction(readonly, func) { const transaction = TransactionManager_1.TransactionManager.newTransaction(readonly); const mysqlTransaction = new MysqlTransaction(this, transaction); try { await mysqlTransaction.begin(); const resp = await func(mysqlTransaction); return resp; } catch (err) { log.error({ err }, 'There was an error running in transaction'); transaction.markError(err); throw err; } finally { await mysqlTransaction.end(); } // return mysqlTransaction.begin() // .then(async () => await func(mysqlTransaction)) // .catch((err) => { // log.error({ err }, 'There was an error running in transaction'); // transaction.markError(err); // throw err; // }) // .then(async (result) => { // await mysqlTransaction.end(); // return result; // }, async (reason) => { // await mysqlTransaction.end(); // throw reason; // }); // .finally((result) => { // mysqlTransaction.end(); // return Promise.resolve(result); // TODO This weird? // }); } shutdown() { if (this.masterPool) { this.masterPool.end(); } if (this.slavePool) { this.slavePool.end(); } } // tslint:disable-next-line:prefer-function-over-method getFullPoolConfig(partial) { const full = { host: 'localhost', port: 3306, user: 'root', password: '', charset: 'utf8', connectionLimit: 10, acquireTimeout: 1000, waitForConnections: true, queueLimit: 10, connectTimeout: 3000, }; Object.assign(full, partial); return full; } getConnectionPoolForReadonly(readonly) { if (readonly && this.slavePool) { return this.slavePool; } else if (this.masterPool) { return this.masterPool; } throw new Error('Couldn\'t find an appropriate connection pool'); } async ping(readonly) { log.debug('Doing ping'); const connection = await getConnectionPromise(this.getConnectionPoolForReadonly(readonly)); log.debug('Got connection for ping'); return await new Promise((resolve, reject) => connection.query('SELECT 1', (err, res) => { log.debug('Result from select'); connection.release(); if (err) { reject(err); } else { resolve(); } })); } } MysqlClient.startMethod = 'initialise'; MysqlClient.stopMethod = 'shutdown'; exports.MysqlClient = MysqlClient; // export const TestUtil = { runQueryOnPool }; //# sourceMappingURL=MysqlClient.js.map