postgre modification detection algorithm

This commit is contained in:
Jan Prochazka 2021-05-15 22:03:23 +02:00
parent b8513b3ecd
commit 7857771056
3 changed files with 59 additions and 120 deletions

View File

@ -117,7 +117,7 @@ export class DatabaseAnalyser {
.filter(x => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change'))
.map(x => x.objectId);
if (filterIds.length == 0) {
res = res.replace(/=OBJECT_ID_CONDITION/g, ' = 0');
res = res.replace(/=OBJECT_ID_CONDITION/g, " = '0'");
} else {
res = res.replace(/=OBJECT_ID_CONDITION/g, ` in (${filterIds.map(x => `'${x}'`).join(',')})`);
}
@ -154,8 +154,8 @@ export class DatabaseAnalyser {
const snapshot = await this._getFastSnapshot();
if (!snapshot) return null;
console.log('STRUCTURE', this.structure);
console.log('SNAPSHOT', snapshot);
// console.log('STRUCTURE', this.structure);
// console.log('SNAPSHOT', snapshot);
const res = [];
for (const field in snapshot) {

View File

@ -58,10 +58,11 @@ class Analyser extends DatabaseAnalyser {
const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions']));
// console.log('PG fkColumns', fkColumns.rows);
return this.mergeAnalyseResult({
return {
tables: tables.rows.map(table => ({
...table,
objectId: `tables:${table.schemaName}.${table.pureName}`,
contentHash: `${table.hashCodeColumns}-${table.hashCodeConstraints}`,
columns: columns.rows
.filter(col => col.pureName == table.pureName && col.schemaName == table.schemaName)
.map(getColumnInfo),
@ -71,6 +72,7 @@ class Analyser extends DatabaseAnalyser {
views: views.rows.map(view => ({
...view,
objectId: `views:${view.schemaName}.${view.pureName}`,
contentHash: view.hashCode,
columns: columns.rows
.filter(col => col.pureName == view.pureName && col.schemaName == view.schemaName)
.map(getColumnInfo),
@ -79,87 +81,50 @@ class Analyser extends DatabaseAnalyser {
.filter(x => x.objectType == 'PROCEDURE')
.map(proc => ({
objectId: `procedures:${proc.schemaName}.${proc.pureName}`,
contentHash: proc.hashCode,
...proc,
})),
functions: routines.rows
.filter(x => x.objectType == 'FUNCTION')
.map(func => ({
objectId: `functions:${func.schemaName}.${func.pureName}`,
contentHash: func.hashCode,
...func,
})),
});
};
}
async getModifications() {
async _getFastSnapshot() {
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
return {
tables: tableModificationsQueryData.rows.map(x => ({
...x,
objectId: `tables:${x.schemaName}.${x.pureName}`,
contentHash: `${x.hashCodeColumns}-${x.hashCodeConstraints}`,
})),
views: viewModificationsQueryData.rows.map(x => ({
...x,
objectId: `views:${x.schemaName}.${x.pureName}`,
contentHash: x.hashCode,
})),
procedures: routineModificationsQueryData.rows
.filter(x => x.objectType == 'PROCEDURE')
.map(x => ({ ...x, objectTypeField: 'procedures' })),
...routineModificationsQueryData.rows
.map(x => ({
...x,
objectId: `procedures:${x.schemaName}.${x.pureName}`,
contentHash: x.hashCode,
})),
functions: 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,
objectId: `${objectTypeField}:${schemaName}.${pureName}`,
}
: {
newName: { schemaName, pureName },
action: 'add',
objectTypeField,
objectId: `${objectTypeField}:${schemaName}.${pureName}`,
};
return action;
});
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,
objectId: `${objectTypeField}:${x.schemaName}.${x.pureName}`,
}));
}
getDeletedObjects(nameArray) {
return [
...this.getDeletedObjectsForField(nameArray, 'tables'),
...this.getDeletedObjectsForField(nameArray, 'views'),
...this.getDeletedObjectsForField(nameArray, 'procedures'),
...this.getDeletedObjectsForField(nameArray, 'functions'),
...this.getDeletedObjectsForField(nameArray, 'triggers'),
];
.map(x => ({
...x,
objectId: `functions:${x.schemaName}.${x.pureName}`,
contentHash: x.hashCode,
})),
};
}
}

View File

@ -1,54 +1,28 @@
module.exports = `
with pkey as
(
select cc.conrelid, 'create constraint ' || cc.conname || ' primary key(' ||
string_agg(a.attname, ', ' order by array_position(cc.conkey, a.attnum)) || ');\\n'
pkey
from pg_catalog.pg_constraint cc
join pg_catalog.pg_class c on c.oid = cc.conrelid
join pg_catalog.pg_attribute a on a.attrelid = cc.conrelid
and a.attnum = any(cc.conkey)
where cc.contype = 'p'
group by cc.conrelid, cc.conname
)
SELECT oid as "objectId", nspname as "schemaName", relname as "pureName",
md5('CREATE TABLE ' || nspname || '.' || relname || E'\\n(\\n' ||
array_to_string(
array_agg(
' ' || column_name || ' ' || type || ' '|| not_null
select infoTables.table_schema as "schemaName", infoTables.table_name as "pureName",
(
select md5(string_agg(
infoColumns.column_name || '|' || infoColumns.data_type || '|' || infoColumns.is_nullable || '|' || coalesce(infoColumns.character_maximum_length, -1)
|| '|' || coalesce(infoColumns.numeric_precision, -1),
',' order by infoColumns.ordinal_position
)) as "hashCodeColumns"
from information_schema.columns infoColumns
where infoColumns.table_schema = infoTables.table_schema and infoColumns.table_name = infoTables.table_name
),
(
select md5(string_agg(
infoConstraints.constraint_name || '|' || infoConstraints.constraint_type ,
',' order by infoConstraints.constraint_name
)) as "hashCodeConstraints"
from information_schema.table_constraints infoConstraints
where infoConstraints.table_schema = infoTables.table_schema and infoConstraints.table_name = infoTables.table_name
)
, E',\\n'
) || E'\\n);\\n' || coalesce((select pkey from pkey where pkey.conrelid = oid),'NO_PK')) as "hashCode"
from
(
SELECT
c.relname, a.attname AS column_name, c.oid,
n.nspname,
pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
case
when a.attnotnull
then 'NOT NULL'
else 'NULL'
END as not_null
FROM pg_class c,
pg_namespace n,
pg_attribute a,
pg_type t
WHERE c.relkind = 'r'
AND a.attnum > 0
AND a.attrelid = c.oid
AND a.atttypid = t.oid
AND n.oid = c.relnamespace
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
AND n.nspname !~ '^pg_toast'
ORDER BY a.attnum
) as tabledefinition
inner join information_schema.tables on tables.table_schema = tabledefinition.nspname and tables.table_name = tabledefinition.relname
and tables.table_type not like '%VIEW%'
where ('tables:' || nspname || '.' || relname) =OBJECT_ID_CONDITION
group by relname, nspname, oid
from information_schema.tables infoTables
where infoTables.table_type not like '%VIEW%'
and ('tables:' || infoTables.table_schema || '.' || infoTables.table_name) =OBJECT_ID_CONDITION
and infoTables.table_schema <> 'pg_catalog'
and infoTables.table_schema <> 'information_schema'
and infoTables.table_schema <> 'pg_internal'
and infoTables.table_schema !~ '^pg_toast'
`;