@verdnatura/myt
Version:
MySQL version control
204 lines (165 loc) • 6.08 kB
JavaScript
const Myt = require('./myt');
const Command = require('./lib/command');
const Push = require('./myt-push');
const fs = require('fs-extra');
const path = require('path');
const connExt = require('./lib/conn');
const SqlString = require('sqlstring');
const spawn = require('child_process').spawn;
class Apply extends Command {
static usage = {
description: 'Initialize database',
params: {
structure: 'Apply only structure',
changes: 'Apply only changes',
realm: 'Name of fixture realm to use',
docker: 'Whether to load git changes from file',
load: 'Commit SHA to save'
},
operand: 'realm'
};
static args = {
alias: {
structure: 's',
changes: 'c',
realm: 'm',
docker: 'k',
load: 'l'
},
boolean: [
'structure',
'changes',
'docker'
],
string: [
'realm',
'load'
],
default: {
remote: 'socket'
}
};
static reporter = {
mockingDate: 'Mocking date functions.',
applyingFixtures: 'Applying fixtures.',
applyingRealm: 'Applying realm fixtures.',
creatingTriggers: 'Creating triggers.',
};
async _run(myt, ctx, cfg, opts) {
const {
structureDir,
dumpDir,
fixturesDir
} = ctx;
const {triggersImport} = cfg;
const conn = await myt.createConnection();
const applyAll = !(opts.structure || opts.changes);
if (ctx.isProtectedRemote)
throw new Error('Cannot apply to protected remote');
if (applyAll || opts.structure) {
await this.importFile(path.join(structureDir, 'before.sql'));
const importFiles = [
'structure.sql',
'data.sql',
];
if (triggersImport == 'before')
importFiles.push('triggers.sql');
importFiles.push('privileges.sql');
for (const file of importFiles)
await this.importFile(path.join(dumpDir, file), true);
await this.importFile(path.join(structureDir, 'after.sql'));
// Mock date functions
this.emit('mockingDate');
const mockDateScript = path.join(structureDir, 'mock-date.sql');
if (cfg.mockDate) {
if (!await fs.pathExists(mockDateScript))
throw new Error(`Date mock enabled but mock script does not exist: ${mockDateScript}`);
let sql = await fs.readFile(mockDateScript, 'utf8');
sql = sql.replace(/@mockDate/g, SqlString.escape(cfg.mockDate));
await connExt.multiQuery(conn, sql);
}
}
if (applyAll || opts.changes) {
// Apply changes
await myt.run(Push, {
triggers: triggersImport == 'after',
docker: true,
load: opts.load,
commit: true
});
// Apply fixtures
this.emit('applyingFixtures');
const fixturesFiles = [
['before.sql'],
['.dump.sql', true],
['after.sql'],
['local.sql'],
];
for (const [file, force] of fixturesFiles)
await this.importFile(path.join(fixturesDir, file), force);
// Apply realms
if (opts.realm) {
this.emit('applyingRealm');
const realmDir = `realms/${opts.realm}`;
let realmFiles = await fs.readdir(realmDir);
realmFiles = realmFiles.map(file => path.parse(file).name);
for (const file of realmFiles) {
await this.importFile(path.join(realmDir, `${file}.sql`));
}
}
// Create triggers
if (triggersImport == 'after') {
this.emit('creatingTriggers');
for (const schema of cfg.schemas) {
const triggersPath = `${ctx.routinesDir}/${schema}/triggers`;
if (!await fs.pathExists(triggersPath))
continue;
const triggersDir = await fs.readdir(triggersPath);
for (const triggerFile of triggersDir)
await connExt.queryFromFile(conn, `${triggersPath}/${triggerFile}`);
}
}
await conn.end();
}
}
async importFile(file, force) {
const {cfg, ctx} = this;
if (!await fs.exists(file)) {
if (cfg.debug)
console.debug('Import:', `${file} does not exist, ignoring`);
return;
}
const iniPath = path.join(ctx.mytDir, 'remotes', ctx.iniFile);
const execArgs = [
`--defaults-file=${iniPath}`,
'--default-character-set=utf8',
'--comments',
];
if (force)
execArgs.push('--force');
let stdio;
const mysqlBin = 'mariadb';
if (cfg.debug === true) {
const quotedArgs = execArgs
.map(x => /\s/g.test(x) ? `"${x}"` : x)
.join(' ');
console.debug('Command:', `${mysqlBin} ${quotedArgs} < ${file}`.yellow);
stdio = ['pipe', 'inherit', 'inherit'];
} else {
stdio = ['pipe', 'ignore', 'ignore'];
}
const child = spawn(mysqlBin, execArgs, {stdio});
fs.createReadStream(file).pipe(child.stdin);
return await new Promise((resolve, reject) => {
child.on('exit', code => {
if (code !== 0)
reject(new Error(`${mysqlBin} exit code ${code}`));
else
resolve(code);
});
});
}
}
module.exports = Apply;
if (require.main === module)
new Myt().cli(Apply);