inceptum
Version:
hipages take on the foundational library for enterprise-grade apps written in NodeJS
240 lines • 8.74 kB
JavaScript
"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