mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
postgre modification detection algorithm
This commit is contained in:
parent
b8513b3ecd
commit
7857771056
@ -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) {
|
||||
|
@ -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,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user