2020-04-11 18:24:30 +00:00
|
|
|
const fp = require('lodash/fp');
|
|
|
|
const _ = require('lodash');
|
|
|
|
const sql = require('./sql');
|
2020-02-02 19:28:27 +00:00
|
|
|
|
2020-04-11 18:24:30 +00:00
|
|
|
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
2020-06-26 13:28:44 +00:00
|
|
|
const { isTypeString, isTypeNumeric } = require('@dbgate/tools');
|
|
|
|
|
|
|
|
function normalizeTypeName(dataType) {
|
|
|
|
if (dataType == 'character varying') return 'varchar';
|
|
|
|
if (dataType == 'timestamp without time zone') return 'timestamp';
|
|
|
|
return dataType;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getColumnInfo({
|
|
|
|
isNullable,
|
|
|
|
isIdentity,
|
|
|
|
columnName,
|
|
|
|
dataType,
|
|
|
|
charMaxLength,
|
|
|
|
numericPrecision,
|
|
|
|
numericScale,
|
|
|
|
defaultValue,
|
|
|
|
}) {
|
|
|
|
const normDataType = normalizeTypeName(dataType);
|
|
|
|
let fullDataType = normDataType;
|
|
|
|
if (charMaxLength && isTypeString(normDataType)) fullDataType = `${normDataType}(${charMaxLength})`;
|
|
|
|
if (numericPrecision && numericScale && isTypeNumeric(normDataType))
|
|
|
|
fullDataType = `${normDataType}(${numericPrecision},${numericScale})`;
|
|
|
|
return {
|
|
|
|
columnName,
|
|
|
|
dataType: fullDataType,
|
2020-06-29 17:49:54 +00:00
|
|
|
notNull: !isNullable || isNullable == 'NO' || isNullable == 'no',
|
2020-06-26 13:28:44 +00:00
|
|
|
autoIncrement: !!isIdentity,
|
|
|
|
defaultValue,
|
|
|
|
};
|
|
|
|
}
|
2020-02-02 19:28:27 +00:00
|
|
|
|
2020-05-16 07:34:28 +00:00
|
|
|
class PostgreAnalyser extends DatabaseAnalayser {
|
2020-02-02 19:28:27 +00:00
|
|
|
constructor(pool, driver) {
|
|
|
|
super(pool, driver);
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:49:54 +00:00
|
|
|
createQuery(resFileName, typeFields) {
|
2020-03-14 09:38:10 +00:00
|
|
|
let res = sql[resFileName];
|
2020-09-28 09:33:25 +00:00
|
|
|
|
|
|
|
if (this.singleObjectFilter) {
|
|
|
|
const { typeField, schemaName, pureName } = this.singleObjectFilter;
|
|
|
|
if (!typeFields || !typeFields.includes(typeField)) return null;
|
2020-09-28 12:33:47 +00:00
|
|
|
res = res.replace(/=OBJECT_ID_CONDITION/g, ` = '${typeField}:${schemaName || 'public'}.${pureName}'`);
|
2020-09-28 09:33:25 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
if (!this.modifications || !typeFields || this.modifications.length == 0) {
|
|
|
|
res = res.replace(/=OBJECT_ID_CONDITION/g, ' is not null');
|
|
|
|
} else {
|
|
|
|
const filterNames = this.modifications
|
|
|
|
.filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change'))
|
|
|
|
.filter((x) => x.newName)
|
|
|
|
.map((x) => `${x.objectTypeField}:${x.newName.schemaName}.${x.newName.pureName}`);
|
|
|
|
if (filterNames.length == 0) {
|
|
|
|
res = res.replace(/=OBJECT_ID_CONDITION/g, ' IS NULL');
|
|
|
|
} else {
|
|
|
|
res = res.replace(/=OBJECT_ID_CONDITION/g, ` in (${filterNames.map((x) => `'${x}'`).join(',')})`);
|
|
|
|
}
|
|
|
|
}
|
2020-02-02 19:28:27 +00:00
|
|
|
return res;
|
2020-09-28 09:33:25 +00:00
|
|
|
|
|
|
|
// let res = sql[resFileName];
|
|
|
|
// res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null');
|
|
|
|
// return res;
|
2020-02-02 19:28:27 +00:00
|
|
|
}
|
2020-04-12 08:16:33 +00:00
|
|
|
async _runAnalysis() {
|
2020-06-29 17:49:54 +00:00
|
|
|
const tables = await this.driver.query(this.pool, this.createQuery('tableModifications', ['tables']));
|
|
|
|
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
|
|
|
const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
|
|
|
const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
|
|
|
const views = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
2020-06-29 18:21:37 +00:00
|
|
|
const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions']));
|
2020-05-16 07:34:28 +00:00
|
|
|
// console.log('PG fkColumns', fkColumns.rows);
|
2020-02-02 19:28:27 +00:00
|
|
|
|
2020-09-28 09:33:25 +00:00
|
|
|
return this.mergeAnalyseResult(
|
|
|
|
{
|
|
|
|
tables: tables.rows.map((table) => ({
|
|
|
|
...table,
|
|
|
|
columns: columns.rows
|
|
|
|
.filter((col) => col.pureName == table.pureName && col.schemaName == table.schemaName)
|
|
|
|
.map(getColumnInfo),
|
|
|
|
primaryKey: DatabaseAnalayser.extractPrimaryKeys(table, pkColumns.rows),
|
|
|
|
foreignKeys: DatabaseAnalayser.extractForeignKeys(table, fkColumns.rows),
|
|
|
|
})),
|
|
|
|
views: views.rows.map((view) => ({
|
|
|
|
...view,
|
|
|
|
columns: columns.rows
|
|
|
|
.filter((col) => col.pureName == view.pureName && col.schemaName == view.schemaName)
|
|
|
|
.map(getColumnInfo),
|
|
|
|
})),
|
|
|
|
procedures: routines.rows.filter((x) => x.objectType == 'PROCEDURE'),
|
|
|
|
functions: routines.rows.filter((x) => x.objectType == 'FUNCTION'),
|
|
|
|
},
|
|
|
|
(x) => `${x.objectTypeField}:${x.schemaName}.${x.pureName}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getModifications() {
|
|
|
|
const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications'));
|
|
|
|
const viewModificationsQueryData = await this.driver.query(this.pool, this.createQuery('viewModifications'));
|
|
|
|
const routineModificationsQueryData = await this.driver.query(this.pool, this.createQuery('routineModifications'));
|
|
|
|
|
|
|
|
const allModifications = _.compact([
|
|
|
|
...tableModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'tables' })),
|
|
|
|
...viewModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'views' })),
|
|
|
|
...routineModificationsQueryData.rows
|
|
|
|
.filter((x) => x.objectType == 'PROCEDURE')
|
|
|
|
.map((x) => ({ ...x, objectTypeField: 'procedures' })),
|
|
|
|
...routineModificationsQueryData.rows
|
|
|
|
.filter((x) => x.objectType == 'FUNCTION')
|
|
|
|
.map((x) => ({ ...x, objectTypeField: 'functions' })),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const modifications = allModifications.map((x) => {
|
|
|
|
const { objectTypeField, hashCode, pureName, schemaName } = x;
|
|
|
|
|
|
|
|
if (!objectTypeField || !this.structure[objectTypeField]) return null;
|
|
|
|
const obj = this.structure[objectTypeField].find((x) => x.pureName == pureName && x.schemaName == schemaName);
|
|
|
|
|
|
|
|
// object not modified
|
|
|
|
if (obj && obj.hashCode == hashCode) return null;
|
|
|
|
|
|
|
|
// console.log('MODIFICATION OF ', objectTypeField, schemaName, pureName);
|
|
|
|
|
|
|
|
/** @type {import('@dbgate/types').DatabaseModification} */
|
|
|
|
const action = obj
|
|
|
|
? {
|
|
|
|
newName: { schemaName, pureName },
|
|
|
|
oldName: _.pick(obj, ['schemaName', 'pureName']),
|
|
|
|
action: 'change',
|
|
|
|
objectTypeField,
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
newName: { schemaName, pureName },
|
|
|
|
action: 'add',
|
|
|
|
objectTypeField,
|
|
|
|
};
|
|
|
|
return action;
|
2020-04-12 08:16:33 +00:00
|
|
|
});
|
2020-09-28 09:33:25 +00:00
|
|
|
|
|
|
|
return [
|
|
|
|
..._.compact(modifications),
|
|
|
|
...this.getDeletedObjects([...allModifications.map((x) => `${x.schemaName}.${x.pureName}`)]),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
getDeletedObjectsForField(nameArray, objectTypeField) {
|
|
|
|
return this.structure[objectTypeField]
|
|
|
|
.filter((x) => !nameArray.includes(`${x.schemaName}.${x.pureName}`))
|
|
|
|
.map((x) => ({
|
|
|
|
oldName: _.pick(x, ['schemaName', 'pureName']),
|
|
|
|
action: 'remove',
|
|
|
|
objectTypeField,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
getDeletedObjects(nameArray) {
|
|
|
|
return [
|
|
|
|
...this.getDeletedObjectsForField(nameArray, 'tables'),
|
|
|
|
...this.getDeletedObjectsForField(nameArray, 'views'),
|
|
|
|
...this.getDeletedObjectsForField(nameArray, 'procedures'),
|
|
|
|
...this.getDeletedObjectsForField(nameArray, 'functions'),
|
|
|
|
...this.getDeletedObjectsForField(nameArray, 'triggers'),
|
|
|
|
];
|
2020-02-02 19:28:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-16 07:34:28 +00:00
|
|
|
module.exports = PostgreAnalyser;
|