larvitdbmigration
Version:
node.js database migration tool
157 lines • 8.18 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const larvitutils_1 = require("larvitutils");
const mysql2_1 = __importDefault(require("mysql2"));
const fs_1 = __importDefault(require("fs"));
const topLogPrefix = 'larvitdbmigration: dbType/mariadb.js: ';
class MariaDbDriver {
/**
* MariaDB driver
*
* @param {object} options -
* @param {String} options.tableName -
* @param {object} options.dbDriver -
* @param {String} options.migrationScriptPath -
* @param {object} options.log -
*/
constructor(options) {
/* istanbul ignore if */
if (!options)
throw new Error('Options parameter is missing');
/* istanbul ignore if */
if (!options.tableName)
throw new Error('Missing required option "tableName"');
/* istanbul ignore if */
if (!options.dbDriver)
throw new Error('Missing option dbDriver');
/* istanbul ignore if */
if (!options.log)
throw new Error('Missing option log');
this.lUtils = new larvitutils_1.Utils({ log: options.log });
this.options = options;
}
async getLock() {
const { lUtils } = this;
const { tableName, log } = this.options;
const logPrefix = topLogPrefix + 'getLock() - tableName: "' + tableName + '" - ';
const db = this.options.dbDriver;
const dbCon = await db.pool.getConnection();
await dbCon.query('LOCK TABLES `' + tableName + '` WRITE;');
const [rows] = await dbCon.query('SELECT running FROM `' + tableName + '`');
/* istanbul ignore next */ // Difficult to test without mocks
if (rows.length === 0) {
const errMsg = 'No database records';
log.error(logPrefix + errMsg);
throw new Error(errMsg);
}
else if (rows[0].running !== 0) {
await dbCon.query('UNLOCK TABLES;');
log.info(logPrefix + 'Another process is running the migrations, wait and try again soon.');
await lUtils.setTimeout(500);
await this.getLock();
}
await dbCon.query('UPDATE `' + tableName + '` SET running = 1');
await dbCon.query('UNLOCK TABLES;');
dbCon.release();
}
async run() {
const { tableName, log } = this.options;
const logPrefix = topLogPrefix + 'run() - tableName: "' + tableName + '" - ';
const db = this.options.dbDriver;
// Create table if it does not exist
await db.query('CREATE TABLE IF NOT EXISTS `' + tableName + '` (`id` tinyint(1) unsigned NOT NULL DEFAULT \'1\', `version` int(10) unsigned NOT NULL DEFAULT \'0\', `running` tinyint(3) unsigned NOT NULL DEFAULT \'0\', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin COMMENT=\'Used for automatic database versioning. Do not modify!\';');
// Update old version of table (for seamless updating of old versions of this module)
const descRes = await db.query('DESCRIBE `' + tableName + '`');
/* istanbul ignore next */
if (descRes.rows.length === 2 && descRes.rows[0].Field === 'version' && descRes.rows[1].Field === 'running') {
// Old version detected! Update!
await db.query('ALTER TABLE `' + tableName + '` ADD `id` tinyint(1) unsigned NOT NULL DEFAULT \'1\' FIRST;');
await db.query('ALTER TABLE `' + tableName + '` ADD PRIMARY KEY `id` (`id`);');
}
// Insert first record if it does not exist
await db.query('INSERT IGNORE INTO `' + tableName + '` VALUES(1, 0, 0);');
// Lock table by setting the running column to 1
await this.getLock();
// Get current version
const verRes = await db.query('SELECT version FROM `' + tableName + '`;');
const curVer = verRes.rows[0].version;
log.info(logPrefix + 'Current database version is ' + curVer);
// Run scripts
await this.runScripts(Number(curVer) + 1);
// Unlock table
await db.query('UPDATE `' + tableName + '` SET running = 0;');
}
async runScripts(startVersion) {
const { tableName, log, migrationScriptPath, context } = this.options;
const logPrefix = topLogPrefix + 'runScripts() - tableName: "' + tableName + '" - ';
const db = this.options.dbDriver;
log.verbose(logPrefix + 'Started with startVersion: "' + startVersion + '" in path: "' + migrationScriptPath + '"');
// Get items in the migration script path
const items = await new Promise((resolve, reject) => {
fs_1.default.readdir(migrationScriptPath, (err, items) => {
/* istanbul ignore if */ // Difficult to test without mocks
if (err) {
log.error(logPrefix + 'Could not read migration script path "' + migrationScriptPath + '", err: ' + err.message);
reject(err);
}
else {
resolve(items);
}
});
});
// Loop through the items and see what kind of migration scripts it is
for (const item of items) {
if (item === startVersion + '.js') {
log.info(logPrefix + 'Found js migration script #' + startVersion + ', running it now.');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const migrationScript = require(migrationScriptPath + '/' + startVersion + '.js');
await migrationScript({ db, log, context });
log.debug(logPrefix + 'Js migration script #' + startVersion + ' ran. Updating database version and moving on.');
await db.query('UPDATE `' + tableName + '` SET version = ' + startVersion + ';');
await this.runScripts(startVersion + 1);
}
else if (item === startVersion + '.sql') {
log.info(logPrefix + 'Found sql migration script #' + startVersion + ', running it now.');
const localDbConf = {};
const validDbOptions = [
'host', 'port', 'localAddress',
'socketPath', 'user', 'password',
'database', 'charset', 'timezone',
'connectTimeout', 'stringifyObjects', 'insecureAuth',
'typeCast', 'queryFormat', 'supportBigNumbers',
'bigNumberStrings', 'dateStrings', 'debug',
'trace', 'multipleStatements', 'flags',
'ssl',
// Valid for pools
'waitForConnections', 'connectionLimit', 'queueLimit',
];
for (const key of Object.keys(db.dbConf)) {
if (validDbOptions.indexOf(key) !== -1) {
localDbConf[key] = db.dbConf[key];
}
}
localDbConf.multipleStatements = true;
const dbCon = mysql2_1.default.createConnection(localDbConf);
await new Promise((resolve, reject) => {
dbCon.query(fs_1.default.readFileSync(migrationScriptPath + '/' + item).toString(), err => {
/* istanbul ignore if */ // Difficult to test without mocks
if (err) {
log.error(logPrefix + 'Migration file: ' + item + ' SQL error: ' + err.message);
return reject(err);
}
log.info(logPrefix + 'Sql migration script #' + startVersion + ' ran. Updating database version and moving on.');
resolve();
});
});
await db.query('UPDATE `' + tableName + '` SET version = ' + startVersion + ';');
dbCon.end();
await this.runScripts(startVersion + 1);
}
}
}
}
exports.default = MariaDbDriver;
//# sourceMappingURL=mariadb.js.map