UNPKG

@freemework/sql.misc.migration

Version:

Hosting library of the Freemework Project.

266 lines 15 kB
"use strict"; 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