UNPKG

@sqb/migrator

Version:

Database migrator for SQB

238 lines (237 loc) 8.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PgMigrationAdapter = void 0; const tslib_1 = require("tslib"); const postgres_1 = require("@sqb/postgres"); const path_1 = tslib_1.__importDefault(require("path")); const postgrejs_1 = require("postgrejs"); const migration_adapter_js_1 = require("../migration-adapter.js"); const migration_package_js_1 = require("../migration-package.js"); const types_js_1 = require("../types.js"); const pgAdapter = new postgres_1.PgAdapter(); class PgMigrationAdapter extends migration_adapter_js_1.MigrationAdapter { constructor() { super(...arguments); this._infoSchema = 'public'; this._version = 0; this._status = types_js_1.MigrationStatus.idle; this.defaultVariables = { tablespace: 'pg_default', schema: 'public', owner: 'postgres', }; this.summaryTable = 'migration_summary'; this.eventTable = 'migration_events'; } get packageName() { return this._migrationPackage.name; } get version() { return this._version; } get status() { return this._status; } get infoSchema() { return this._infoSchema; } get summaryTableFull() { return this.infoSchema + '.' + this.summaryTable; } get eventTableFull() { return this.infoSchema + '.' + this.eventTable; } static async create(options) { // Create connection const connection = (await pgAdapter.connect(options.connection)) .intlcon; try { const adapter = new PgMigrationAdapter(); adapter._connection = connection; adapter._migrationPackage = options.migrationPackage; adapter._infoSchema = options.infoSchema || '__migration'; adapter.defaultVariables.schema = options.connection.schema || ''; if (!adapter.defaultVariables.schema) { const r = await connection.query('SELECT CURRENT_SCHEMA ', { objectRows: true, }); adapter.defaultVariables.schema = r.rows?.[0]?.current_schema || 'public'; } // Check if migration schema await connection.query(`CREATE SCHEMA IF NOT EXISTS ${adapter.infoSchema} AUTHORIZATION postgres;`); // Create summary table if not exists await connection.execute(` CREATE TABLE IF NOT EXISTS ${adapter.summaryTableFull} ( package_name varchar not null, status varchar(16) not null, current_version integer not null default 0, created_at timestamp without time zone not null default current_timestamp, updated_at timestamp without time zone, CONSTRAINT pk_${adapter.summaryTable} PRIMARY KEY (package_name) )`); // Create events table if not exists await connection.execute(` CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull} ( id serial not null, package_name varchar not null, version integer not null default 0, event varchar(16) not null, event_time timestamp without time zone not null, title text, message text not null, filename text, details text, CONSTRAINT pk_${adapter.eventTable} PRIMARY KEY (id) )`); // Insert summary record if not exists const r = await connection.query(`SELECT status FROM ${adapter.summaryTableFull} WHERE package_name = $1`, { params: [adapter.packageName], objectRows: true, }); if (!(r && r.rows?.length)) { await connection.query(`insert into ${adapter.summaryTableFull} (package_name, status) values ($1, $2)`, { params: [adapter.packageName, types_js_1.MigrationStatus.idle], }); } await adapter.refresh(); return adapter; } catch (e) { await connection.close(0); throw e; } } async close() { await this._connection.close(); } async refresh() { const r = await this._connection.query(`SELECT * FROM ${this.summaryTableFull} WHERE package_name = $1`, { params: [this.packageName], objectRows: true, }); const row = r.rows && r.rows[0]; if (!row) throw new Error('Summary record did not created'); this._version = row.current_version; this._status = row.status; } async update(info) { let sql = ''; const params = []; if (info.status && info.status !== this.status) { params.push(info.status); sql += ',\n status = $' + params.length; } if (info.version && info.version !== this.version) { params.push(info.version); sql += ',\n current_version = $' + params.length; } if (sql) { params.push(this.packageName); sql = `update ${this.summaryTableFull} set updated_at = current_timestamp` + sql + `\n where package_name =$` + params.length; await this._connection.query(sql, { params }); if (info.status) this._status = info.status; if (info.version) this._version = info.version; } } async writeEvent(event) { const sql = `insert into ${this.eventTableFull} ` + '(package_name, version, event, event_time, title, message, filename, details) ' + 'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5, $6, $7)'; await this._connection.query(sql, { params: [ this.packageName, event.version, event.event, event.title, event.message, event.filename, event.details, ], }); } async executeTask(migrationPackage, migration, task, variables) { variables = { ...this.defaultVariables, ...variables, }; if ((0, migration_package_js_1.isSqlScriptMigrationTask)(task)) { try { let script; if (typeof task.script === 'function') { script = await task.script({ migrationPackage, migration, task, variables, }); } else script = task.script; if (typeof script !== 'string') return; script = this.replaceVariables(script, variables); await this._connection.execute(script); } catch (e) { let msg = `Error in task "${task.title}"`; if (task.filename) msg += '\n at ' + path_1.default.relative(migrationPackage.baseDir, task.filename); if (e.lineNr) { if (!task.filename) e.message += '\n at'; msg += ` (${e.lineNr},${e.colNr}):\n` + e.line; if (e.colNr) msg += '\n' + ' '.repeat(e.colNr - 1) + '^'; } e.message = msg + '\n\n' + e.message; throw e; } return; } if ((0, migration_package_js_1.isCustomMigrationTask)(task)) { await task.fn(this._connection, this); return; } if ((0, migration_package_js_1.isInsertDataMigrationTask)(task)) { const tableName = this.replaceVariables(task.tableName, variables); const script = task.rows .map(row => this.rowToSql(tableName, row)) .join('\n'); await this._connection.execute(script); } } backupDatabase() { return Promise.resolve(undefined); } lockSchema() { return Promise.resolve(undefined); } restoreDatabase() { return Promise.resolve(undefined); } unlockSchema() { return Promise.resolve(undefined); } rowToSql(tableName, row) { let sql = ''; const keys = Object.keys(row); sql += `insert into ${tableName} (${keys}) values (`; for (let i = 0; i < keys.length; i++) { sql += (i ? ', ' : '') + (0, postgrejs_1.stringifyValueForSQL)(row[keys[i]]); } sql += ');\n'; return sql; } } exports.PgMigrationAdapter = PgMigrationAdapter;