UNPKG

pg-test-util

Version:

PostgreSQL administrative utilities such as creating and dropping tables, users etc.

208 lines 11.5 kB
"use strict"; var _Database_instances, _Database_cleanup, _Database_clientConfig, _Database_schemaOids, _Database_isConnected, _Database_tables, _Database_views, _Database_materializedViews, _Database_partitionedTables, _Database_sequences, _Database_includedSchemas, _Database_getError; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); /* eslint-disable no-return-assign */ const fs_1 = require("fs"); const path_1 = require("path"); const pg_1 = require("pg"); const escape = require("pg-escape"); const verror_1 = tslib_1.__importDefault(require("verror")); const { readFile } = fs_1.promises; /** Execute tasks related to individual database such as connecting, querying, getting tables, getting sequences etc. */ class Database { /** @ignore */ constructor(client, { clientConfig, schemas = ["public"], cleanup, drop }) { _Database_instances.add(this); _Database_cleanup.set(this, void 0); _Database_clientConfig.set(this, void 0); _Database_schemaOids.set(this, {}); _Database_isConnected.set(this, false); _Database_tables.set(this, []); _Database_views.set(this, []); _Database_materializedViews.set(this, []); _Database_partitionedTables.set(this, []); _Database_sequences.set(this, []); _Database_includedSchemas.set(this, void 0); this.client = client; tslib_1.__classPrivateFieldSet(this, _Database_clientConfig, clientConfig, "f"); tslib_1.__classPrivateFieldSet(this, _Database_cleanup, cleanup, "f"); this.drop = drop; tslib_1.__classPrivateFieldSet(this, _Database_includedSchemas, schemas, "f"); } /** @ignore */ static async new(args) { const database = new Database(new pg_1.Client(args.clientConfig), args); await database.connect(); return database; } /** Name of the database */ get name() { return tslib_1.__classPrivateFieldGet(this, _Database_clientConfig, "f").database; } /** Connects to database. */ async connect() { if (tslib_1.__classPrivateFieldGet(this, _Database_isConnected, "f")) return; try { tslib_1.__classPrivateFieldSet(this, _Database_isConnected, true, "f"); await this.client.connect(); } catch (error) { throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, "Cannot connect client"); } } /** Disconnects from database. */ async disconnect() { if (!tslib_1.__classPrivateFieldGet(this, _Database_isConnected, "f")) return; try { tslib_1.__classPrivateFieldSet(this, _Database_isConnected, false, "f"); await this.client.end(); } catch (error) { throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, "Cannot disconnect client"); } } /** Fetches database objects (i.e. tables, sequences) from database and refreshes the cache of the object. If you create new tables etc., you should refresh. */ async refresh() { if (Object.keys(tslib_1.__classPrivateFieldGet(this, _Database_schemaOids, "f")).length === 0) { const schemas = await this.queryFile((0, path_1.join)(__dirname, "../sql/schema.sql"), [tslib_1.__classPrivateFieldGet(this, _Database_includedSchemas, "f")]); schemas.forEach((schema) => (tslib_1.__classPrivateFieldGet(this, _Database_schemaOids, "f")[schema.name] = schema.oid)); } const entityOidMapper = {}; const entities = await this.queryFile((0, path_1.join)(__dirname, "../sql/entity.sql"), [Object.values(tslib_1.__classPrivateFieldGet(this, _Database_schemaOids, "f"))]); const columns = await this.queryFile((0, path_1.join)(__dirname, "../sql/column.sql"), [Object.values(tslib_1.__classPrivateFieldGet(this, _Database_schemaOids, "f"))]); tslib_1.__classPrivateFieldSet(this, _Database_tables, [], "f"); tslib_1.__classPrivateFieldSet(this, _Database_views, [], "f"); tslib_1.__classPrivateFieldSet(this, _Database_materializedViews, [], "f"); tslib_1.__classPrivateFieldSet(this, _Database_partitionedTables, [], "f"); tslib_1.__classPrivateFieldSet(this, _Database_sequences, [], "f"); entities.forEach((entity) => { entityOidMapper[entity.oid] = entity; if (entity.kindName === "table") tslib_1.__classPrivateFieldGet(this, _Database_tables, "f").push({ schema: entity.schema, name: entity.name }); else if (entity.kindName === "view") tslib_1.__classPrivateFieldGet(this, _Database_views, "f").push({ schema: entity.schema, name: entity.name }); else if (entity.kindName === "materialized view") tslib_1.__classPrivateFieldGet(this, _Database_materializedViews, "f").push({ schema: entity.schema, name: entity.name }); else if (entity.kindName === "partitioned table") tslib_1.__classPrivateFieldGet(this, _Database_partitionedTables, "f").push({ schema: entity.schema, name: entity.name }); }); const sequenceRegExp = /nextval\(['"]+(.+?)['"]+::regclass\)/; columns.forEach((column) => { const match = column.defaultWithTypeCast?.match(sequenceRegExp); if (match) { const entity = entityOidMapper[column.parentOid]; tslib_1.__classPrivateFieldGet(this, _Database_sequences, "f").push({ schema: entity.schema, table: entity.name, column: column.name, name: match[1] }); } }); } /** Returns tables from database. Uses cache for fast results. Use `refresh()` method to refresh the cache. */ async getTables() { if (tslib_1.__classPrivateFieldGet(this, _Database_tables, "f").length === 0) await this.refresh(); return tslib_1.__classPrivateFieldGet(this, _Database_tables, "f"); } /** Returns views from database. Uses cache for fast results. Use `refresh()` method to refresh the cache. */ async getViews() { if (tslib_1.__classPrivateFieldGet(this, _Database_tables, "f").length === 0) await this.refresh(); return tslib_1.__classPrivateFieldGet(this, _Database_views, "f"); } /** Returns materialized views from database. Uses cache for fast results. Use `refresh()` method to refresh the cache. */ async getMaterializedViews() { if (tslib_1.__classPrivateFieldGet(this, _Database_tables, "f").length === 0) await this.refresh(); return tslib_1.__classPrivateFieldGet(this, _Database_materializedViews, "f"); } /** Returns partitioned tables from database. Uses cache for fast results. Use `refresh()` method to refresh the cache. */ async getPartitionedTables() { if (tslib_1.__classPrivateFieldGet(this, _Database_tables, "f").length === 0) await this.refresh(); return tslib_1.__classPrivateFieldGet(this, _Database_partitionedTables, "f"); } /** Returns sequences from database. Uses cache for fast results. Use `refresh()` method to refresh the cache. */ async getSequences() { if (tslib_1.__classPrivateFieldGet(this, _Database_sequences, "f").length === 0) await this.refresh(); return tslib_1.__classPrivateFieldGet(this, _Database_sequences, "f"); } /** Set current value of sequence for each column of all tables based on record with maximum number. If there are no record in the table, the value will be set to 1. */ async syncSequences() { const sequences = await this.getSequences(); try { await Promise.all(sequences.map((seq) => this.client.query(escape("SELECT setval($1, (SELECT COALESCE((SELECT %I + 1 FROM %I ORDER BY %I DESC LIMIT 1), 1)), false)", seq.column, seq.table, seq.column), [`${seq.schema}.${seq.name}`]))); } catch (error) { throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, "Cannot update sequences"); } } /** * Truncates all tables and resets their sequences in the database. * * @param ignore are the list of the tables to ignore. */ async truncate({ ignore = [] } = {}) { const tables = await this.getTables(); const ignoreSet = new Set(ignore); const tablesToTruncate = tables .filter((table) => !ignoreSet.has(`${table.schema}.${table.name}`) && !ignoreSet.has(`${table.name}`)) .map((table) => `${escape(table.schema)}.${escape(table.name)}`); if (tablesToTruncate.length === 0) return; try { await this.client.query(`TRUNCATE ${tablesToTruncate.join(", ")} RESTART IDENTITY`); } catch (error) { await tslib_1.__classPrivateFieldGet(this, _Database_cleanup, "f").call(this); throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, `Cannot truncate tables`); } } /** * Executes given SQL and returns result rows. * * @typeparam T is type for single row returned by SQL query. * * @param sql is sql query. * @param params are array of parameters to pass query. * @returns result rows of the SQL query. */ async query(sql, params) { await this.connect(); try { // Remove utf BOM from start. BOM causes syntax error in postgres. const nonBOMSql = sql.charCodeAt(0) === 0xfeff ? sql.slice(1) : sql; const result = await this.client.query(nonBOMSql, params); return result.rows; } catch (error) { throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, `Cannot execute SQL query`); } } /** * Reads and executes SQL in given file and returns results. * * @typeparam T is type for single row returned by SQL query. * * @param file is file to read SQL from. * @param params are array of parameters to pass query. * @returns result rows of the SQL query. */ async queryFile(file, params) { try { const sql = await readFile(file, { encoding: "utf8" }); const rows = await this.query(sql, params); return rows; } catch (error) { throw await tslib_1.__classPrivateFieldGet(this, _Database_instances, "m", _Database_getError).call(this, error, `Cannot execute SQL file '${file}'`); } } } exports.default = Database; _Database_cleanup = new WeakMap(), _Database_clientConfig = new WeakMap(), _Database_schemaOids = new WeakMap(), _Database_isConnected = new WeakMap(), _Database_tables = new WeakMap(), _Database_views = new WeakMap(), _Database_materializedViews = new WeakMap(), _Database_partitionedTables = new WeakMap(), _Database_sequences = new WeakMap(), _Database_includedSchemas = new WeakMap(), _Database_instances = new WeakSet(), _Database_getError = async function _Database_getError(error, message) { await tslib_1.__classPrivateFieldGet(this, _Database_cleanup, "f").call(this); return new verror_1.default(error, `${message} for '${this.name}' database`); }; //# sourceMappingURL=database.js.map