@freemework/sql.misc.migration
Version:
Hosting library of the Freemework Project.
266 lines • 15 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FSqlMigrationManager = exports.FSqlMigrationManagerLoggerLabel = void 0;
const os_1 = require("os");
const path = __importStar(require("path"));
const vm = __importStar(require("vm"));
const common_1 = require("@freemework/common");
const f_sql_migration_sources_js_1 = require("./f_sql_migration_sources.js");
class FSqlMigrationManagerLoggerLabel extends common_1.FLoggerLabel {
static DIRECTION = new FSqlMigrationManagerLoggerLabel("migration.direction", "Describes a direction of migration process (install/rollback)");
static SCRIPT = new FSqlMigrationManagerLoggerLabel("migration.script", "Describes a name of migration script");
static VERSION = new FSqlMigrationManagerLoggerLabel("migration.version", "Describes a version of migration atom");
}
exports.FSqlMigrationManagerLoggerLabel = FSqlMigrationManagerLoggerLabel;
class FSqlMigrationManager {
_sqlConnectionFactory;
_versionTableName;
logger;
constructor(opts) {
this._sqlConnectionFactory = opts.sqlConnectionFactory;
this._versionTableName = opts.versionTableName !== undefined ? opts.versionTableName : "__migration";
this.logger = common_1.FLogger.create(this.constructor.name);
}
/**
* Install versions (increment version)
* @param executionContext
* @param targetVersion Optional target version. Will use latest version if omitted.
*/
async install(executionContext, migrationSources, targetVersion) {
executionContext = new common_1.FLoggerLabelsExecutionContext(executionContext, FSqlMigrationManagerLoggerLabel.DIRECTION.value("install"));
const currentVersion = await this.getCurrentVersion(executionContext);
const availableVersions = [...migrationSources.versionNames].sort(); // from old version to new version
let scheduleVersions = availableVersions;
if (currentVersion !== null) {
scheduleVersions = scheduleVersions.reduce((p, c) => { if (c > currentVersion) {
p.push(c);
} return p; }, []);
}
if (targetVersion !== undefined) {
scheduleVersions = scheduleVersions.reduceRight(function (p, c) {
if (c <= targetVersion) {
p.unshift(c);
}
return p;
}, []);
}
await this.sqlConnectionFactory.usingConnection(executionContext, async (usingExecutionContext, sqlConnection) => {
if (!(await this._isVersionTableExist(usingExecutionContext, sqlConnection))) {
await this._createVersionTable(usingExecutionContext, sqlConnection);
}
});
for (const versionName of scheduleVersions) {
await this.sqlConnectionFactory.usingConnectionWithTransaction(new common_1.FLoggerLabelsExecutionContext(executionContext, FSqlMigrationManagerLoggerLabel.VERSION.value(versionName)), async (usingExecutionContext, sqlConnection) => {
const migrationLogger = new FSqlMigrationManager.MigrationLogger(this.logger);
const versionBundle = migrationSources.getVersionBundle(versionName);
const installScriptNames = [...versionBundle.installScriptNames].sort();
for (const scriptName of installScriptNames) {
const scriptExecutionContext = new common_1.FLoggerLabelsExecutionContext(usingExecutionContext, FSqlMigrationManagerLoggerLabel.SCRIPT.value(scriptName));
const script = versionBundle.getInstallScript(scriptName);
switch (script.kind) {
case f_sql_migration_sources_js_1.FSqlMigrationSources.Script.Kind.SQL: {
migrationLogger.info(scriptExecutionContext, `Execute SQL script: ${script.name}`);
migrationLogger.trace(scriptExecutionContext, os_1.EOL + script.content);
await this._executeMigrationSql(scriptExecutionContext, sqlConnection, migrationLogger, script.content);
break;
}
case f_sql_migration_sources_js_1.FSqlMigrationSources.Script.Kind.JAVASCRIPT: {
migrationLogger.info(scriptExecutionContext, `Execute JS script: ${script.name}`);
migrationLogger.trace(scriptExecutionContext, os_1.EOL + script.content);
await this._executeMigrationJavaScript(scriptExecutionContext, sqlConnection, migrationLogger, {
content: script.content,
file: script.file
});
break;
}
default:
migrationLogger.warn(scriptExecutionContext, `Skip script '${versionName}:${script.name}' due unknown kind of script`);
}
}
const logText = migrationLogger.flush();
await this._insertVersionLog(usingExecutionContext, sqlConnection, versionName, logText);
const rollbackScripts = versionBundle.rollbackScriptNames.map(scriptName => versionBundle.getRollbackScript(scriptName));
await this._insertRollbackScripts(usingExecutionContext, sqlConnection, versionName, rollbackScripts);
});
}
}
/**
* Rollback versions (increment version)
* @param cancellationToken A cancellation token that can be used to cancel the action.
* @param targetVersion Optional target version. Will rollback all versions if omitted.
*/
async rollback(executionContext, targetVersion) {
executionContext = new common_1.FLoggerLabelsExecutionContext(executionContext, FSqlMigrationManagerLoggerLabel.DIRECTION.value("rollback"));
const currentVersion = await this.getCurrentVersion(executionContext);
if (currentVersion === null) {
this.logger.warn(executionContext, `Skip rollback due to no any installed versions`);
return;
}
const versionNames = await this.sqlConnectionFactory.usingConnection(executionContext, (usingExecutionContext, sqlConnection) => this._listVersions(usingExecutionContext, sqlConnection));
const availableVersions = [...versionNames].sort().reverse(); // from new version to old version
let scheduleVersionNames = availableVersions;
scheduleVersionNames = scheduleVersionNames.reduce((p, c) => { if (c <= currentVersion) {
p.push(c);
} return p; }, []);
if (targetVersion !== undefined) {
scheduleVersionNames = scheduleVersionNames.reduceRight((p, c) => { if (c > targetVersion) {
p.unshift(c);
} return p; }, []);
}
for (const versionName of scheduleVersionNames) {
await this.sqlConnectionFactory.usingConnectionWithTransaction(new common_1.FLoggerLabelsExecutionContext(executionContext, FSqlMigrationManagerLoggerLabel.VERSION.value(versionName)), async (usingExecutionContext, sqlConnection) => {
if (!await this._isVersionLogExist(usingExecutionContext, sqlConnection, versionName)) {
this.logger.warn(executionContext, `Skip rollback for version '${versionName}' due this does not present inside database.`);
return;
}
const scripts = await this._getRollbackScripts(usingExecutionContext, sqlConnection, versionName);
//const versionBundle: FSqlMigrationSources.VersionBundle = this._migrationSources.getVersionBundle(versionName);
const rollbackScriptNames = [...scripts.map(s => s.name)].sort().reverse();
const scriptsMap = scripts.reduce((acc, curr) => { acc.set(curr.name, curr); return acc; }, new Map());
for (const scriptName of rollbackScriptNames) {
const scriptExecutionContext = new common_1.FLoggerLabelsExecutionContext(usingExecutionContext, FSqlMigrationManagerLoggerLabel.SCRIPT.value(scriptName));
const script = scriptsMap.get(scriptName);
switch (script.kind) {
case f_sql_migration_sources_js_1.FSqlMigrationSources.Script.Kind.SQL: {
this.logger.info(scriptExecutionContext, `Execute SQL script: ${script.name}`);
this.logger.trace(scriptExecutionContext, os_1.EOL + script.content);
await this._executeMigrationSql(scriptExecutionContext, sqlConnection, this.logger, script.content);
break;
}
case f_sql_migration_sources_js_1.FSqlMigrationSources.Script.Kind.JAVASCRIPT: {
this.logger.info(scriptExecutionContext, `Execute JS script: ${script.name}`);
this.logger.trace(scriptExecutionContext, os_1.EOL + script.content);
await this._executeMigrationJavaScript(scriptExecutionContext, sqlConnection, this.logger, {
content: script.content,
file: script.file
});
break;
}
default:
this.logger.warn(scriptExecutionContext, `Skip script '${versionName}:${script.name}' due unknown kind of script`);
}
}
await this._removeVersionLog(usingExecutionContext, sqlConnection, versionName);
});
}
}
get sqlConnectionFactory() { return this._sqlConnectionFactory; }
get versionTableName() { return this._versionTableName; }
async _executeMigrationJavaScript(executionContext, sqlConnection, migrationLogger, migrationJavaScript) {
await new Promise((resolve, reject) => {
const sandbox = {
__private: { executionContext, log: migrationLogger, resolve, reject, sqlConnection: sqlConnection },
__dirname: path.dirname(migrationJavaScript.file),
__filename: migrationJavaScript.file
};
const script = new vm.Script(`${migrationJavaScript.content}
Promise.resolve().then(() => migration(__private.executionContext, __private.sqlConnection, __private.log)).then(__private.resolve).catch(__private.reject);`, {
filename: migrationJavaScript.file
});
script.runInNewContext(sandbox, { displayErrors: false });
});
}
async _executeMigrationSql(executionContext, sqlConnection, migrationLogger, sqlText) {
migrationLogger.trace(executionContext, os_1.EOL + sqlText);
await sqlConnection.statement(sqlText).execute(executionContext);
}
async _getRollbackScripts(executionContext, _sqlConnection, _version) {
// TODO Make abstract method
this.logger.fatal(executionContext, "_getRollbackScripts: Not implemented yet");
return [];
}
async _insertRollbackScripts(executionContext, _sqlConnection, _version, _scripts) {
// TODO Make abstract method
this.logger.fatal(executionContext, "_insertRollbackScripts: Not implemented yet");
}
async _listVersions(executionContext, _sqlConnection) {
// TODO Make abstract method
this.logger.fatal(executionContext, "_listVersions: Not implemented yet");
return [];
}
}
exports.FSqlMigrationManager = FSqlMigrationManager;
(function (FSqlMigrationManager) {
class MigrationException extends common_1.FException {
}
FSqlMigrationManager.MigrationException = MigrationException;
class WrongMigrationDataException extends MigrationException {
}
FSqlMigrationManager.WrongMigrationDataException = WrongMigrationDataException;
class MigrationLogger extends common_1.FLoggerBase {
_wrap;
_lines;
constructor(wrap) {
super(wrap.name);
this._lines = [];
this._wrap = wrap;
}
flush() {
// Join and empty _lines
return this._lines.splice(0).join(os_1.EOL);
}
writeToOutput(level, labelValues, message, ex) {
let levelTxt;
switch (level) {
case common_1.FLoggerLevel.DEBUG:
levelTxt = "[DEBUG]";
break;
case common_1.FLoggerLevel.INFO:
levelTxt = "[INFO]";
break;
case common_1.FLoggerLevel.WARN:
levelTxt = "[WARN]";
break;
case common_1.FLoggerLevel.ERROR:
levelTxt = "[ERROR]";
break;
case common_1.FLoggerLevel.FATAL:
levelTxt = "[FATAL]";
break;
default:
levelTxt = "[TRACE]";
break;
}
this._wrap.log(labelValues, level, message, ex);
this._lines.push(`${levelTxt} ${message}`);
}
isLevelEnabled(_level) {
return true; // this produce [TRACE] logs
}
}
FSqlMigrationManager.MigrationLogger = MigrationLogger;
})(FSqlMigrationManager || (exports.FSqlMigrationManager = FSqlMigrationManager = {}));
//# sourceMappingURL=f_sql_migration_manager.js.map