igo
Version:
Igo is a Node.js Web Framework based on Express
135 lines (112 loc) • 3.61 kB
JavaScript
const _ = require('lodash');
const fs = require('fs/promises');
const path = require('path');
const config = require('../config');
const logger = require('../logger');
const utils = require('../utils');
//
module.exports.init = async (db) => {
if (!config.auto_migrate) return;
try {
const { connection } = await db.getConnection();
const { dialect } = db.driver;
const lock = db.config.database + '.__db_migrations';
const getLock = dialect.getLock(lock);
const res = await db.driver.query(connection, getLock, []);
if (!dialect.gotLock(res)) {
// could not get lock, skip migration
return db.driver.release(connection);
}
// got lock, migrate!
await module.exports.migrate(db);
// release lock
setTimeout(async () => {
const releaseLock = dialect.releaseLock(lock);
await db.driver.query(connection, releaseLock);
db.driver.release(connection);
}, 10000);
} catch (err) {
console.error(err);
}
};
//
module.exports.initmigrations = async (db) => {
const sql = db.driver.dialect.createMigrationsTable;
await db.query(sql);
};
//
module.exports.list = async (db) => {
await module.exports.initmigrations(db);
const sql = db.driver.dialect.listMigrations;
return await db.query(sql);
};
//
module.exports.migrate = async (db, rootDir = '.') => {
if (db.config.noMigrations) {
return;
}
const sqldir = `${rootDir}/${db.config.migrations_dir || 'sql'}`;
let querybuf = '';
// create directory if it does not exist
await fs.mkdir(sqldir, { recursive: true });
// execute SQL line
const executeLine = async (line) => {
line = line.replace('\r', '').trim();
if (line.match('^--')) return;
if (line.match('\\;$')) {
querybuf += line;
if (config.mysql.debugsql) logger.info(querybuf);
await db.query(querybuf);
querybuf = '';
} else if (line.length > 0) {
querybuf += line + ' ';
return;
} else {
return;
}
};
// execute SQL migration file
const executeFile = async (file) => {
if (!file.filename.match('[0-9]{8}.*\\.sql$')) {
logger.warn('File %s does not match migration pattern, skipping', file.filename);
return;
}
// find if file has already been played
const result = await db.query(db.driver.dialect.findMigration, [file.filename]);
if (result && result.length > 0) {
return 'alreadyplayed';
}
try {
const data = await fs.readFile(file.path);
const lines = data.toString().split('\n');
if (config.mysql.debugsql) {
logger.info('Executing ' + file.path + ': ' + lines.length + ' lines to process');
}
for (const line of lines) {
await executeLine(line);
}
// no error
await db.query(db.driver.dialect.insertMigration, [file.filename, true, null, new Date()]);
logger.info('✅ ' + file.filename);
} catch (err) {
await db.query(db.driver.dialect.insertMigration, [file.filename, false, String(err), new Date()]);
logger.info('❌ ' + file.filename);
logger.error('SQL error in file %s', file.path);
throw err;
}
}
let files = [];
const filenames = await fs.readdir(sqldir);
_.forEach(filenames, (filename) => {
files.push({ filename, path: path.join(sqldir, filename) });
})
files = _.sortBy(files, 'filename');
await module.exports.initmigrations(db);
try {
for (const file of files) {
await executeFile(file);
}
} catch (err) {
logger.error(err);
}
};