UNPKG

@freemework/sql.postgres

Version:

Postgres SQL Facade library of the Freemework Project.

180 lines 9.11 kB
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