From 5b688cf949ee96e6552253bd4607e5f37d4d839c Mon Sep 17 00:00:00 2001 From: Khushboo Vashi Date: Wed, 12 Aug 2020 17:36:48 +0530 Subject: [PATCH] Resolve schema diff dependencies by selecting the appropriate node automatically and maintain the order in the generated script. Fixes #5730 --- docs/en_US/release_notes_4_25.rst | 3 +- web/pgadmin/tools/schema_diff/__init__.py | 11 + .../static/js/schema_diff.backform.js | 1 + .../static/js/schema_diff_dependency.js | 237 ++++++++++++++++++ .../schema_diff/static/js/schema_diff_ui.js | 54 +++- 5 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 web/pgadmin/tools/schema_diff/static/js/schema_diff_dependency.js diff --git a/docs/en_US/release_notes_4_25.rst b/docs/en_US/release_notes_4_25.rst index ac57b0e2a..860aeb866 100644 --- a/docs/en_US/release_notes_4_25.rst +++ b/docs/en_US/release_notes_4_25.rst @@ -43,4 +43,5 @@ Bug fixes | `Issue #5710 `_ - Fixed an issue when comparing the table with a trigger throwing error in schema diff. | `Issue #5713 `_ - Corrected DROP SQL syntax for catalog. | `Issue #5716 `_ - Fixed an issue where ajax call continues to fire even after disconnect the database server. -| `Issue #5724 `_ - Clarify some of the differences when running in server mode in the docs. \ No newline at end of file +| `Issue #5724 `_ - Clarify some of the differences when running in server mode in the docs. +| `Issue #5730 `_ - Resolve schema diff dependencies by selecting the appropriate node automatically and maintain the order in the generated script. \ No newline at end of file diff --git a/web/pgadmin/tools/schema_diff/__init__.py b/web/pgadmin/tools/schema_diff/__init__.py index c3f78ea03..6d348988c 100644 --- a/web/pgadmin/tools/schema_diff/__init__.py +++ b/web/pgadmin/tools/schema_diff/__init__.py @@ -344,6 +344,17 @@ def get_server(sid, did): ) @login_required def connect_server(sid): + # Check if server is already connected then no need to reconnect again. + driver = get_driver(PG_DEFAULT_DRIVER) + manager = driver.connection_manager(sid) + conn = manager.connection() + if conn.connected(): + return make_json_response( + success=1, + info=gettext("Server connected."), + data={} + ) + server = Server.query.filter_by(id=sid).first() view = SchemaDiffRegistry.get_node_view('server') return view.connect(server.servergroup_id, sid) diff --git a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js index 705b3ec90..8191e6e14 100644 --- a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js +++ b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js @@ -514,6 +514,7 @@ let SchemaDiffFooterView = Backform.Form.extend({ return this; }, }); + export { SchemaDiffSelect2Control, SchemaDiffHeaderView, diff --git a/web/pgadmin/tools/schema_diff/static/js/schema_diff_dependency.js b/web/pgadmin/tools/schema_diff/static/js/schema_diff_dependency.js new file mode 100644 index 000000000..940172a0a --- /dev/null +++ b/web/pgadmin/tools/schema_diff/static/js/schema_diff_dependency.js @@ -0,0 +1,237 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +function handleDependencies() { + event.stopPropagation(); + let isChecked = event.target.checked || (event.target.checked === undefined && + event.target.className && event.target.className.indexOf('unchecked') == -1); + + let isHeaderSelected = event.target.id.includes('header-selector'); + + if (this.gridContext && this.gridContext.rowIndex && _.isUndefined(this.gridContext.row.rows)) { + // Single Row Selection + let rowData = this.grid.getData().getItem(this.gridContext.rowIndex); + this.gridContext = {}; + if (rowData.status) { + let depRows = this.selectDependencies(rowData, isChecked); + this.selectedRowCount = this.grid.getSelectedRows().length; + if (isChecked && depRows.length > 0) + this.grid.setSelectedRows(depRows); + else if (!isChecked) + this.grid.setSelectedRows(this.grid.getSelectedRows().filter(x => !depRows.includes(x))); + + this.ddlCompare(rowData); + } + } else if((this.gridContext && this.gridContext.row && !_.isUndefined(this.gridContext.row.rows)) || + this.selectedRowCount != this.grid.getSelectedRows().length) { + + // Group / All Rows Selection + this.selectedRowCount = this.grid.getSelectedRows().length; + + if (this.gridContext.row && this.gridContext.row.__group) { + let context = this.gridContext; + this.gridContext = {}; + this.selectDependenciesForGroup(isChecked, context); + } else { + this.gridContext = {}; + this.selectDependenciesForAll(isChecked, isHeaderSelected); + } + } + + if (this.grid.getSelectedRows().length > 0) { + this.header.$el.find('button#generate-script').removeAttr('disabled'); + } else { + this.header.$el.find('button#generate-script').attr('disabled', true); + } +} + +function selectDependenciesForGroup(isChecked, context) { + let self = this, + finalRows = []; + + if (!isChecked) { + _.each(context.row.rows, function(row) { + if (row && row.status && row.status.toLowerCase() != 'identical' ) { + let d = self.selectDependencies(row, isChecked); + finalRows = finalRows.concat(d); + } + }); + } + else { + _.each(self.grid.getSelectedRows(), function(row) { + let data = self.grid.getData().getItem(row); + if (data.status && data.status.toLowerCase() != 'identical') { + finalRows = finalRows.concat(self.selectDependencies(data, isChecked)); + } + }); + } + + finalRows = [...new Set(finalRows)]; + + if (isChecked) + self.grid.setSelectedRows(finalRows); + else { + let filterRows = []; + filterRows = self.grid.getSelectedRows().filter(x => !finalRows.includes(x)); + + self.selectedRowCount = filterRows.length; + self.grid.setSelectedRows(filterRows); + } +} + +function selectDependenciesForAll(isChecked, isHeaderSelected) { + let self = this, + finalRows = []; + + if(!isChecked && isHeaderSelected) { + self.dataView.getItems().map(function(el) { + el.dependentCount = []; + el.dependLevel = 1; + }); + self.selectedRowCount = 0; + return; + } + + _.each(self.grid.getSelectedRows(), function(row) { + let data = self.grid.getData().getItem(row); + if (data.status) { + finalRows = finalRows.concat(self.selectDependencies(data, isChecked)); + } + }); + + finalRows = [...new Set(finalRows)]; + + if (isChecked && finalRows.length > 0) + self.grid.setSelectedRows(finalRows); + else if (!isChecked) { + let filterRows = []; + filterRows = self.grid.getSelectedRows().filter(x => !finalRows.includes(x)); + + self.selectedRowCount = filterRows.length; + self.grid.setSelectedRows(filterRows); + } +} + + +function selectDependencies(data, isChecked) { + let self = this, + rows = [], + setDependencies = undefined, + setOrigDependencies = undefined, + finalRows = []; + + if (!data.dependLevel || !isChecked) data.dependLevel = 1; + if (!data.dependentCount || !isChecked) data.dependentCount = []; + + if (data.status && data.status.toLowerCase() == 'identical') { + self.selectedRowCount = self.grid.getSelectedRows().length; + return []; + } + + setDependencies = function(rowData, dependencies, isChecked) { + _.each(dependencies, function(dependency) { + if (dependency.length == 0) return; + let dependencyData = []; + + dependencyData = self.dataView.getItems().filter(item => item.type == dependency.type && item.oid == dependency.oid); + + if (dependencyData.length > 0) { + dependencyData = dependencyData[0]; + if (!dependencyData.dependentCount) dependencyData.dependentCount = []; + + let groupData = []; + + if (dependencyData.status && dependencyData.status.toLowerCase() != 'identical') { + groupData = self.dataView.getGroups().find( + (item) => { if (dependencyData.group_name == item.groupingKey) return item.groups; } + ); + + if (groupData && groupData.groups) { + groupData = groupData.groups.find( + (item) => { return item.groupingKey == dependencyData.group_name + ':|:' + dependencyData.type; } + ); + + if (groupData && groupData.collapsed == 1) + self.dataView.expandGroup(dependencyData.group_name + ':|:' + dependencyData.type); + } + + if (isChecked || _.isUndefined(isChecked)) { + dependencyData.dependLevel = rowData.dependLevel + 1; + if (dependencyData.dependentCount.indexOf(rowData.oid) === -1) + dependencyData.dependentCount.push(rowData.oid); + rows[rows.length] = dependencyData; + } else { + dependencyData.dependentCount.splice(dependencyData.dependentCount.indexOf(rowData.oid), 1); + if (dependencyData.dependentCount.length == 0) { + rows[rows.length] = dependencyData; + dependencyData.dependLevel = 1; + } + } + } + if (Object.keys(dependencyData.dependencies).length > 0) { + if (dependencyData.dependentCount.indexOf(rowData.oid) !== -1 ) { + let depCirRows = dependencyData.dependencies.filter(x => x.oid !== rowData.oid); + if (!dependencyData.orig_dependencies) + dependencyData.orig_dependencies = Object.assign([], dependencyData.dependencies); + dependencyData.dependencies = depCirRows; + } + setDependencies(dependencyData, dependencyData.dependencies, isChecked); + } + } + }); + }; + + setDependencies(data, data.dependencies, isChecked); + + setOrigDependencies = function(dependencies) { + _.each(dependencies, function(dependency) { + if (dependency.length == 0) return; + let dependencyData = []; + + dependencyData = self.dataView.getItems().filter(item => item.type == dependency.type && item.oid == dependency.oid); + + if (dependencyData.length > 0) { + dependencyData = dependencyData[0]; + + if (dependencyData.orig_dependencies && Object.keys(dependencyData.orig_dependencies).length > 0) { + if (!dependencyData.dependentCount) dependencyData.dependentCount = []; + + if (dependencyData.status && dependencyData.status.toLowerCase() != 'identical') { + dependencyData.dependencies = dependencyData.orig_dependencies; + dependencyData.orig_dependencies = []; + } + if (dependencyData.dependencies.length > 0) { + setOrigDependencies(dependencyData.dependencies); + } + } + } + }); + }; + + setOrigDependencies(data.dependencies); + + if (isChecked) finalRows = self.grid.getSelectedRows(); + + _.each(rows, function(row) { + let r = self.grid.getData().getRowByItem(row); + if(!_.isUndefined(r) && finalRows.indexOf(r) === -1 ) { + finalRows.push(self.grid.getData().getRowByItem(row)); + } + }); + + self.selectedRowCount = finalRows.length; + return finalRows; +} + +export { + handleDependencies, + selectDependenciesForGroup, + selectDependenciesForAll, + selectDependencies, +}; diff --git a/web/pgadmin/tools/schema_diff/static/js/schema_diff_ui.js b/web/pgadmin/tools/schema_diff/static/js/schema_diff_ui.js index 3fff0e115..ad23cedf4 100644 --- a/web/pgadmin/tools/schema_diff/static/js/schema_diff_ui.js +++ b/web/pgadmin/tools/schema_diff/static/js/schema_diff_ui.js @@ -19,9 +19,12 @@ import {generateScript} from 'tools/datagrid/static/js/show_query_tool'; import 'pgadmin.sqleditor'; import pgWindow from 'sources/window'; -import {SchemaDiffSelect2Control, SchemaDiffHeaderView, +import { SchemaDiffSelect2Control, SchemaDiffHeaderView, SchemaDiffFooterView, SchemaDiffSqlControl} from './schema_diff.backform'; +import { handleDependencies, selectDependenciesForGroup, + selectDependenciesForAll, selectDependencies } from './schema_diff_dependency'; + var wcDocker = window.wcDocker; export default class SchemaDiffUI { @@ -198,10 +201,11 @@ export default class SchemaDiffUI { script_header; script_header = gettext('-- This script was generated by a beta version of the Schema Diff utility in pgAdmin 4. \n'); - script_header += gettext('-- This version does not include dependency resolution, and may require manual changes \n'); - script_header += gettext('-- to the script to ensure changes are applied in the correct order.\n'); + script_header += gettext('-- For the circular dependencies, the order in which Schema Diff writes the objects is not very sophisticated \n'); + script_header += gettext('-- and may require manual changes to the script to ensure changes are applied in the correct order.\n'); script_header += gettext('-- Please report an issue for any failure with the reproduction steps. \n'); + _.each(url_params, function(key, val) { url_params[key] = parseInt(val, 10); }); @@ -249,14 +253,22 @@ export default class SchemaDiffUI { }; if (sel_rows.length > 0) { - let script_body = ''; + let script_array = {1: [], 2: [], 3: [], 4: [], 5: []}, + script_body = ''; for (var row = 0; row < sel_rows.length; row++) { let data = self.grid.getData().getItem(sel_rows[row]); if(!_.isUndefined(data.diff_ddl)) { - script_body += data.diff_ddl + '\n\n'; + if (!(data.dependLevel in script_array)) script_array[data.dependLevel] = []; + script_array[data.dependLevel].push(data.diff_ddl); } } + _.each(Object.keys(script_array).reverse(), function(s) { + if (script_array[s].length > 0) { + script_body += script_array[s].join('\n') + '\n\n'; + } + }); + generated_script = script_header + 'BEGIN;' + '\n' + script_body + 'END;'; open_query_tool(); } else if (!_.isUndefined(self.model.get('diff_ddl'))) { @@ -362,9 +374,10 @@ export default class SchemaDiffUI { // Change Row css on the basis of item status self.dataView.getItemMetadata = function(row) { - var item = self.dataView.getItem(row); + let item = self.dataView.getItem(row), + group_item = groupItemMetadataProvider.getGroupRowMetadata(item); if (item.__group) { - return groupItemMetadataProvider.getGroupRowMetadata(item); + return group_item; } if(item.status === 'Different') { @@ -393,6 +406,17 @@ export default class SchemaDiffUI { self.dataView.syncGridSelection(grid, true, true); + grid.onMouseEnter.subscribe(function (evt) { + var cell = grid.getCellFromEvent(evt); + self.gridContext = {}; + self.gridContext.rowIndex = cell.row; + self.gridContext.row = grid.getDataItem(cell.row); + }.bind(self)); + + grid.onMouseLeave.subscribe(function () { + self.gridContext = {}; + }); + grid.onClick.subscribe(function(e, args) { if (args.row) { data = args.grid.getData().getItem(args.row); @@ -400,9 +424,9 @@ export default class SchemaDiffUI { } }.bind(self)); - grid.onSelectedRowsChanged.subscribe(self.handle_generate_button.bind(self)); + grid.onSelectedRowsChanged.subscribe(self.handleDependencies.bind(this)); - self.model.on('change:diff_ddl', self.handle_generate_button.bind(self)); + self.model.on('change:diff_ddl', self.handleDependencies.bind(self)); $('#schema-diff-grid').on('keyup', function() { if ((event.keyCode == 38 || event.keyCode ==40) && this.grid.getActiveCell().row) { @@ -414,11 +438,10 @@ export default class SchemaDiffUI { self.render_grid_data(data); } - - render_grid_data(data) { var self = this; self.grid.setSelectedRows([]); + self.selected_row_count = self.grid.getSelectedRows().length; data.sort((a, b) => (a.label > b.label) ? 1 : (a.label === b.label) ? ((a.title > b.title) ? 1 : -1) : -1); self.dataView.beginUpdate(); self.dataView.setItems(data); @@ -503,7 +526,7 @@ export default class SchemaDiffUI { 'source_ddl': undefined, 'target_ddl': undefined, 'diff_ddl': undefined, - }); + }, {silent: true}); if(data.status && data.status.toLowerCase() == 'identical') { var url_params = self.selection; @@ -541,7 +564,7 @@ export default class SchemaDiffUI { 'source_ddl': data.source_ddl, 'target_ddl': data.target_ddl, 'diff_ddl': data.diff_ddl, - }); + }, {silent: true}); self.footer.render(); } @@ -855,3 +878,8 @@ export default class SchemaDiffUI { }); } } + +SchemaDiffUI.prototype.handleDependencies = handleDependencies; +SchemaDiffUI.prototype.selectDependenciesForGroup = selectDependenciesForGroup; +SchemaDiffUI.prototype.selectDependenciesForAll = selectDependenciesForAll; +SchemaDiffUI.prototype.selectDependencies = selectDependencies;