@freemework/sql.postgres
Version:
Postgres SQL Facade library of the Freemework Project.
180 lines • 9.11 kB
JavaScript
import { FExceptionInvalidOperation, } from "@freemework/common";
import { FSqlMigrationManager, FSqlMigrationSources } from "@freemework/sql.misc.migration";
export class FSqlMigrationManagerPostgres extends FSqlMigrationManager {
_schema;
constructor(opts) {
super(opts);
this._schema = opts.sqlConnectionFactory.defaultSchema;
}
async getCurrentVersion(executionContext) {
return await this.sqlConnectionFactory.usingConnection(executionContext, async (sqlConnection) => {
const isExist = await this._isVersionTableExist(executionContext, sqlConnection);
if (isExist === false) {
return null;
}
await this._verifyVersionTableStructure(executionContext, sqlConnection);
const versionData = await sqlConnection.statement(`SELECT "version" FROM "${this._schema}"."${this.versionTableName}" ORDER BY "version" DESC LIMIT 1`).executeScalarOrNull(executionContext);
if (versionData === null) {
return null;
}
return versionData.asString;
});
}
async listVersions(executionContext) {
return await this.sqlConnectionFactory.usingConnection(executionContext, async (sqlConnection) => {
const isExist = await this._isVersionTableExist(executionContext, sqlConnection);
if (isExist === false) {
return [];
}
await this._verifyVersionTableStructure(executionContext, sqlConnection);
const versionRows = await sqlConnection
.statement(`SELECT "version" FROM "${this._schema}"."${this.versionTableName}" ORDER BY "version" DESC`)
.executeQuery(executionContext);
return versionRows.map(versionRow => versionRow.get("version").asString);
});
}
async _createVersionTable(executionContext, sqlProvider) {
await sqlProvider.statement(`CREATE SCHEMA IF NOT EXISTS "${this._schema}"`).execute(executionContext);
const tables = await sqlProvider.statement(`SELECT "tablename" FROM "pg_catalog"."pg_tables" WHERE "schemaname" != 'pg_catalog' AND "schemaname" != 'information_schema' AND "schemaname" = $1 AND "tablename" != 'emptytestflag'`).executeQuery(executionContext, this._schema);
if (tables.length > 0) {
const tablesString = tables.slice(0, 5).map(sqlData => sqlData.get(0).asString).join(", ") + "..";
throw new FSqlMigrationManager.MigrationException(`Your database has tables: ${tablesString}. Create Version Table allowed only for an empty database. Please create Version Table yourself.`);
}
const views = await sqlProvider.statement(`SELECT "viewname" FROM "pg_catalog"."pg_views" WHERE "schemaname" != 'pg_catalog' AND "schemaname" != 'information_schema' AND "schemaname" = $1`).executeQuery(executionContext, this._schema);
if (views.length > 0) {
const viewsString = views.slice(0, 5).map(sqlData => sqlData.get(0).asString).join(", ") + "..";
throw new FSqlMigrationManager.MigrationException(`Your database has views: ${viewsString}. Create Version Table allowed only for an empty database. Please create Version Table yourself.`);
}
await sqlProvider.statement(`
CREATE TABLE "${this._schema}"."${this.versionTableName}" (
"id" SMALLSERIAL NOT NULL PRIMARY KEY,
"version" VARCHAR(64) NOT NULL UNIQUE,
"utc_deployed_at" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT(now() AT TIME ZONE 'utc'),
"log" TEXT NOT NULL
)
`).execute(executionContext);
await sqlProvider.statement(`
CREATE TABLE "${this._schema}"."${this.versionTableName}_rollback_script" (
"id" SMALLSERIAL NOT NULL PRIMARY KEY,
"version_id" SMALLINT NOT NULL,
"name" VARCHAR(256) NOT NULL,
"kind" VARCHAR(32) NOT NULL,
"file" VARCHAR(2048) NOT NULL,
"content" TEXT NOT NULL,
CONSTRAINT "fk__${this.versionTableName}_rollback_script__${this.versionTableName}"
FOREIGN KEY ("version_id")
REFERENCES "${this._schema}"."${this.versionTableName}" ("id")
)
`).execute(executionContext);
}
async _insertRollbackScripts(executionContext, sqlConnection, version, scripts) {
for (const script of scripts) {
await sqlConnection.statement(`
INSERT INTO "${this._schema}"."${this.versionTableName}_rollback_script" (
"version_id",
"name",
"kind",
"file",
"content"
)
VALUES(
(
SELECT "id"
FROM "${this._schema}"."${this.versionTableName}"
WHERE "version" = $1
),
$2,
$3,
$4,
$5
)
`).execute(executionContext,
/*1*/ version,
/*2*/ script.name,
/*3*/ script.kind,
/*4*/ script.file,
/*5*/ script.content);
}
}
async _insertVersionLog(executionContext, sqlConnection, version, logText) {
await sqlConnection.statement(`INSERT INTO "${this._schema}"."${this.versionTableName}"("version", "log") VALUES($1, $2)`).execute(executionContext, version, logText);
}
async _isVersionLogExist(executionContext, sqlConnection, version) {
const isExistSqlData = await sqlConnection.statement(`SELECT 1 FROM "${this._schema}"."${this.versionTableName}" ` +
`WHERE "version" = $1`).executeScalarOrNull(executionContext, version);
if (isExistSqlData === null) {
return false;
}
if (isExistSqlData.asInteger !== 1) {
throw new FSqlMigrationManager.MigrationException("Unexpected SQL result");
}
return true;
}
async _isVersionTableExist(executionContext, sqlConnection) {
const isExistSqlData = await sqlConnection.statement(`SELECT 1 FROM "pg_catalog"."pg_tables" WHERE "schemaname" != 'pg_catalog' AND "schemaname" != 'information_schema' AND "schemaname" = $1 AND "tablename" = $2`).executeScalarOrNull(executionContext, this._schema, this.versionTableName);
if (isExistSqlData === null) {
return false;
}
if (isExistSqlData.asInteger !== 1) {
throw new FSqlMigrationManager.MigrationException("Unexpected SQL result");
}
return true;
}
async _removeVersionLog(executionContext, sqlConnection, version) {
await sqlConnection.statement(`
DELETE FROM "${this._schema}"."${this.versionTableName}_rollback_script"
WHERE "version_id" = (
SELECT "id"
FROM "${this._schema}"."${this.versionTableName}"
WHERE "version" = $1
)
`).execute(executionContext, version);
await sqlConnection.statement(`DELETE FROM "${this._schema}"."${this.versionTableName}" ` +
`WHERE "version" = $1`).execute(executionContext, version);
}
async _getRollbackScripts(executionContext, sqlConnection, version) {
const sqlRecords = await sqlConnection
.statement(`
SELECT "id", "name", "kind", "file", "content"
FROM "__migration_rollback_script"
WHERE "version_id" = (SELECT "id" FROM "__migration" WHERE "version" = $1)
`)
.executeQuery(executionContext, version);
const scripts = sqlRecords.map(sqlRecord => {
const id = sqlRecord.get("id").asInteger;
const name = sqlRecord.get("name").asString;
const file = sqlRecord.get("file").asString;
const content = sqlRecord.get("content").asString;
const kindStr = sqlRecord.get("kind").asString;
let kind;
switch (kindStr) {
case FSqlMigrationSources.Script.Kind.JAVASCRIPT:
case FSqlMigrationSources.Script.Kind.SQL:
case FSqlMigrationSources.Script.Kind.UNKNOWN:
kind = kindStr;
break;
default:
throw new FExceptionInvalidOperation(`Cannot read a script (id:${id}) from database due not supported kind '${kindStr}'.`);
}
return new FSqlMigrationSources.Script(name, kind, file, content);
});
return scripts;
}
async _verifyVersionTableStructure(executionContext, sqlConnection) {
const isExist = await this._isVersionTableExist(executionContext, sqlConnection);
if (isExist === false) {
throw new FSqlMigrationManager.MigrationException(`The database does not have version table: "${this._schema}"."${this.versionTableName}"`);
}
// TODO check columns
// SELECT * FROM information_schema.columns WHERE table_schema = '${this._schema}' AND table_name = '${this.versionTableName}'
}
async _listVersions(executionContext, sqlConnection) {
// get the version SQL rows as an array
const sqlRecords = await sqlConnection
.statement(`SELECT "version" FROM "__migration"`)
.executeQuery(executionContext);
const versions = sqlRecords.map(sqlRecord => sqlRecord.get("version").asString);
return versions;
}
}
//# sourceMappingURL=f_sql_migration_manager_postgres.js.map