UNPKG

dbgate-tools

Version:

Auxiliary tools for other DbGate packages.

372 lines (371 loc) 15.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DatabaseAnalyser = void 0; const sortBy_1 = __importDefault(require("lodash/sortBy")); const groupBy_1 = __importDefault(require("lodash/groupBy")); const pick_1 = __importDefault(require("lodash/pick")); const compact_1 = __importDefault(require("lodash/compact")); const getLogger_1 = require("./getLogger"); const schemaInfoTools_1 = require("./schemaInfoTools"); const stringTools_1 = require("./stringTools"); const logger = (0, getLogger_1.getLogger)('dbAnalyser'); const STRUCTURE_FIELDS = [ 'tables', 'collections', 'views', 'matviews', 'functions', 'procedures', 'triggers', 'schedulerEvents', ]; const fp_pick = arg => array => (0, pick_1.default)(array, arg); function mergeTableRowCounts(info, rowCounts) { return { ...info, tables: (info.tables || []).map(table => { var _a, _b; return ({ ...table, tableRowCount: (_b = (_a = rowCounts.find(x => x.objectId == table.objectId)) === null || _a === void 0 ? void 0 : _a.tableRowCount) !== null && _b !== void 0 ? _b : table.tableRowCount, }); }), }; } function areDifferentRowCounts(db1, db2) { for (const t1 of db1.tables || []) { const t2 = (db2.tables || []).find(x => x.objectId == t1.objectId); if ((t1 === null || t1 === void 0 ? void 0 : t1.tableRowCount) !== (t2 === null || t2 === void 0 ? void 0 : t2.tableRowCount)) { return true; } } return false; } class DatabaseAnalyser { constructor(dbhan, driver, version) { this.dbhan = dbhan; this.driver = driver; this.singleObjectId = null; this.dialect = ((driver === null || driver === void 0 ? void 0 : driver.dialectByVersion) && (driver === null || driver === void 0 ? void 0 : driver.dialectByVersion(version))) || (driver === null || driver === void 0 ? void 0 : driver.dialect); this.logger = logger; } async _runAnalysis() { return DatabaseAnalyser.createEmptyStructure(); } async _getFastSnapshot() { return null; } async _computeSingleObjectId() { } addEngineField(db) { var _a; if (!((_a = this.driver) === null || _a === void 0 ? void 0 : _a.engine)) return; for (const field of STRUCTURE_FIELDS) { if (!db[field]) continue; for (const item of db[field]) { item.engine = this.driver.engine; } } db.engine = this.driver.engine; return db; } getLogDbInfo() { return this.driver.getLogDbInfo(this.dbhan); } async fullAnalysis() { logger.debug(this.getLogDbInfo(), 'DBGM-00126 Performing full analysis'); const res = this.addEngineField(await this._runAnalysis()); // console.log('FULL ANALYSIS', res); return res; } async singleObjectAnalysis(name, typeField) { var _a, _b, _c; // console.log('Analysing SINGLE OBJECT', name, typeField); this.singleObjectFilter = { ...name, typeField }; await this._computeSingleObjectId(); const res = this.addEngineField(await this._runAnalysis()); // console.log('SINGLE OBJECT RES', JSON.stringify(res, null, 2)); const obj = ((_a = res[typeField]) === null || _a === void 0 ? void 0 : _a.length) == 1 ? (_b = res[typeField]) === null || _b === void 0 ? void 0 : _b.find(x => x.pureName.toLowerCase() == name.pureName.toLowerCase()) : (_c = res[typeField]) === null || _c === void 0 ? void 0 : _c.find(x => x.pureName == name.pureName && x.schemaName == name.schemaName); // console.log('SINGLE OBJECT', obj); return obj; } async incrementalAnalysis(structure) { logger.info(this.getLogDbInfo(), 'DBGM-00127 Performing incremental analysis'); this.structure = structure; const modifications = await this.getModifications(); if (modifications == null) { // modifications not implemented, perform full analysis this.structure = null; return this.addEngineField(await this._runAnalysis()); } const structureModifications = modifications.filter(x => x.action != 'setTableRowCounts'); const setTableRowCounts = modifications.find(x => x.action == 'setTableRowCounts'); let structureWithRowCounts = null; if (setTableRowCounts) { const newStructure = mergeTableRowCounts(structure, setTableRowCounts.rowCounts); if (areDifferentRowCounts(structure, newStructure)) { structureWithRowCounts = newStructure; } } if (structureModifications.length == 0) { return structureWithRowCounts ? this.addEngineField(structureWithRowCounts) : null; } this.modifications = structureModifications; if (structureWithRowCounts) this.structure = structureWithRowCounts; logger.info({ ...this.getLogDbInfo(), modifications: this.modifications }, 'DBGM-00128 DB modifications detected'); return this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis())); } mergeAnalyseResult(newlyAnalysed) { if (this.structure == null) { return { ...DatabaseAnalyser.createEmptyStructure(), ...newlyAnalysed, }; } const res = {}; for (const field of STRUCTURE_FIELDS) { const removedIds = this.modifications .filter(x => x.action == 'remove' && x.objectTypeField == field) .map(x => x.objectId); const newArray = newlyAnalysed[field] || []; const addedChangedIds = newArray.map(x => x.objectId); const removeAllIds = [...removedIds, ...addedChangedIds]; res[field] = (0, sortBy_1.default)([...(this.structure[field] || []).filter(x => !removeAllIds.includes(x.objectId)), ...newArray], x => x.pureName); // merge missing data from old structure for (const item of res[field]) { const original = (this.structure[field] || []).find(x => x.objectId == item.objectId); if (original) { for (const key in original) { if (!item[key]) item[key] = original[key]; } } } } return res; // const {tables,views, functions, procedures, triggers} = this.structure; // return { // tables: // } } getRequestedObjectPureNames(objectTypeField, allPureNames) { if (this.singleObjectFilter) { const { typeField, pureName } = this.singleObjectFilter; if (typeField == objectTypeField) return [pureName]; } if (this.modifications) { return this.modifications .filter(x => x.objectTypeField == objectTypeField) .filter(x => x.newName) .map(x => x.newName.pureName); } return allPureNames; } // findObjectById(id) { // return this.structure.tables.find((x) => x.objectId == id); // } // containsObjectIdCondition(typeFields) { // return this.createQueryCore('=OBJECT_ID_CONDITION', typeFields) != ' is not null'; // } getDefaultSchemaNameCondition() { return 'is not null'; } createQuery(template, typeFields, replacements = {}) { let query = this.createQueryCore(this.processQueryReplacements(template, replacements), typeFields); const dbname = this.dbhan.database; const schemaCondition = (0, schemaInfoTools_1.isCompositeDbName)(dbname) ? `= '${(0, schemaInfoTools_1.splitCompositeDbName)(dbname).schema}' ` : ` ${this.getDefaultSchemaNameCondition()} `; return query === null || query === void 0 ? void 0 : query.replace(/=SCHEMA_NAME_CONDITION/g, schemaCondition); } processQueryReplacements(query, replacements) { for (const repl in replacements) { query = query.replaceAll(repl, replacements[repl]); } return query; } createQueryCore(template, typeFields) { // let res = template; if (this.singleObjectFilter) { const { typeField } = this.singleObjectFilter; if (!this.singleObjectId) return null; if (!typeFields || !typeFields.includes(typeField)) return null; return template.replace(/=OBJECT_ID_CONDITION/g, ` = '${this.singleObjectId}'`); } if (!this.modifications || !typeFields || this.modifications.length == 0) { return template.replace(/=OBJECT_ID_CONDITION/g, ' is not null'); } if (this.modifications.some(x => typeFields.includes(x.objectTypeField) && x.action == 'all')) { // do not filter objects return template.replace(/=OBJECT_ID_CONDITION/g, ' is not null'); } const filterIds = this.modifications .filter(x => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) .map(x => x.objectId); if (filterIds.length == 0) { return null; } return template.replace(/=OBJECT_ID_CONDITION/g, ` in (${filterIds.map(x => `'${x}'`).join(',')})`); } getDeletedObjectsForField(snapshot, objectTypeField) { const items = snapshot[objectTypeField]; if (!items) return []; if (!this.structure[objectTypeField]) return []; return this.structure[objectTypeField] .filter(x => !items.find(y => x.objectId == y.objectId)) .map(x => ({ oldName: (0, pick_1.default)(x, ['schemaName', 'pureName']), objectId: x.objectId, action: 'remove', objectTypeField, })); } getDeletedObjects(snapshot) { return [ ...this.getDeletedObjectsForField(snapshot, 'tables'), ...this.getDeletedObjectsForField(snapshot, 'collections'), ...this.getDeletedObjectsForField(snapshot, 'views'), ...this.getDeletedObjectsForField(snapshot, 'matviews'), ...this.getDeletedObjectsForField(snapshot, 'procedures'), ...this.getDeletedObjectsForField(snapshot, 'functions'), ...this.getDeletedObjectsForField(snapshot, 'triggers'), ...this.getDeletedObjectsForField(snapshot, 'schedulerEvents'), ]; } feedback(obj) { if (this.dbhan.feedback) { this.dbhan.feedback(obj); } if (obj && obj.analysingMessage) { logger.debug(this.getLogDbInfo(), obj.analysingMessage); } } async getModifications() { const snapshot = await this._getFastSnapshot(); if (!snapshot) return null; // console.log('STRUCTURE', this.structure); // console.log('SNAPSHOT', snapshot); const res = []; for (const field in snapshot) { const items = snapshot[field]; if (items === null) { res.push({ objectTypeField: field, action: 'all' }); continue; } if (items === undefined) { // skip - undefined meens, that field is not supported continue; } for (const item of items) { const { objectId, schemaName, pureName, contentHash } = item; const obj = this.structure[field].find(x => x.objectId == objectId); if (obj && contentHash && obj.contentHash == contentHash) continue; const action = obj ? { newName: { schemaName, pureName }, oldName: (0, pick_1.default)(obj, ['schemaName', 'pureName']), action: 'change', objectTypeField: field, objectId, } : { newName: { schemaName, pureName }, action: 'add', objectTypeField: field, objectId, }; res.push(action); } } const rowCounts = (snapshot.tables || []) .filter(x => x.tableRowCount != null) .map(x => ({ objectId: x.objectId, tableRowCount: x.tableRowCount, })); if (rowCounts.length > 0) { res.push({ action: 'setTableRowCounts', rowCounts, }); } return [...(0, compact_1.default)(res), ...this.getDeletedObjects(snapshot)]; } async analyserQuery(template, typeFields, replacements = {}) { const sql = this.createQuery(template, typeFields, replacements); if (!sql) { return { rows: [], }; } try { const res = await this.driver.query(this.dbhan, sql); this.logger.debug({ ...this.getLogDbInfo(), rows: res.rows.length, template }, `DBGM-00129 Loaded analyser query`); return res; } catch (err) { logger.error((0, stringTools_1.extractErrorLogData)(err, { template, ...this.getLogDbInfo() }), 'DBGM-00130 Error running analyser query'); return { rows: [], isError: true, }; } } static createEmptyStructure() { return { tables: [], collections: [], views: [], matviews: [], functions: [], procedures: [], triggers: [], schedulerEvents: [], }; } static byTableFilter(table) { return x => x.pureName == table.pureName && x.schemaName == table.schemaName; } static extractPrimaryKeys(table, pkColumns) { const filtered = pkColumns.filter(DatabaseAnalyser.byTableFilter(table)); if (filtered.length == 0) return undefined; return { ...(0, pick_1.default)(filtered[0], ['constraintName', 'schemaName', 'pureName']), constraintType: 'primaryKey', columns: filtered.map(fp_pick('columnName')), }; } static extractForeignKeys(table, fkColumns) { const grouped = (0, groupBy_1.default)(fkColumns.filter(DatabaseAnalyser.byTableFilter(table)), 'constraintName'); return Object.keys(grouped).map(constraintName => ({ constraintName, constraintType: 'foreignKey', ...(0, pick_1.default)(grouped[constraintName][0], [ 'constraintName', 'schemaName', 'pureName', 'refSchemaName', 'refTableName', 'updateAction', 'deleteAction', ]), columns: grouped[constraintName].map(fp_pick(['columnName', 'refColumnName'])), })); } } exports.DatabaseAnalyser = DatabaseAnalyser;