@sqb/migrator
Version:
Database migrator for SQB
238 lines (237 loc) • 8.74 kB
JavaScript
;
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;