diff --git a/packages/tools/src/DatabaseAnalyser.ts b/packages/tools/src/DatabaseAnalyser.ts index e8a995175..70a02dbfc 100644 --- a/packages/tools/src/DatabaseAnalyser.ts +++ b/packages/tools/src/DatabaseAnalyser.ts @@ -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) { diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js index 90382d049..a8bc8ae13 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js @@ -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, + })), + }; } } diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/tableModifications.js b/plugins/dbgate-plugin-postgres/src/backend/sql/tableModifications.js index 4af0e34b8..158b82062 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/sql/tableModifications.js +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/tableModifications.js @@ -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' `;