diff --git a/docs/en_US/images/preferences_schema_diff.png b/docs/en_US/images/preferences_schema_diff.png index e9381da73..d195e1ba4 100644 Binary files a/docs/en_US/images/preferences_schema_diff.png and b/docs/en_US/images/preferences_schema_diff.png differ diff --git a/docs/en_US/preferences.rst b/docs/en_US/preferences.rst index 121ac350f..858ca7b60 100644 --- a/docs/en_US/preferences.rst +++ b/docs/en_US/preferences.rst @@ -390,6 +390,9 @@ Expand the *Schema Diff* node to specify your display preferences. :alt: Preferences schema diff :align: center +Use the *Ignore whitespaces* switch to ignores the whitespaces while comparing +the string objects. Whitespace includes space, tabs, and CRLF. + Use the *Open in new browser tab* switch to indicate if you would like Schema Diff to open in a new tab. diff --git a/docs/en_US/release_notes_4_23.rst b/docs/en_US/release_notes_4_23.rst index f47d23e3f..0e8716d03 100644 --- a/docs/en_US/release_notes_4_23.rst +++ b/docs/en_US/release_notes_4_23.rst @@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o New features ************ +| `Issue #5468 `_ - Added option to ignore the whitespaces while comparing objects in schema diff. +| `Issue #5500 `_ - Added server group name while selecting servers in schema diff. | `Issue #5516 `_ - Added support of Row Security Policies. | `Issue #5576 `_ - Improve error messaging if the storage and log directories cannot be created. @@ -29,4 +31,5 @@ Bug fixes | `Issue #5507 `_ - Fixed connection and version number detection issue when the database server is upgraded. | `Issue #5521 `_ - Fixed an issue when dumping servers from a desktop pgAdmin app by providing an option '--sqlite-path'. | `Issue #5539 `_ - Fixed typo in exception keyword. +| `Issue #5584 `_ - Fixed an issue where two identical tables showing different by schema diff tool. | `Issue #5592 `_ - Ensure that pgadmin should be able to connect to the server which has password more than 1000 characters. \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py index 5b2aa4236..ef17d6f33 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py @@ -158,7 +158,7 @@ def column_formatter(conn, tid, clid, data, edit_types_list=None, # We will need present type in edit mode edit_types_list.append(data['cltype']) - data['edit_types'] = edit_types_list + data['edit_types'] = sorted(edit_types_list) data['cltype'] = DataTypeReader.parse_type_name(data['cltype']) @@ -205,7 +205,7 @@ def get_formatted_columns(conn, tid, data, other_columns, edit_types.keys()))) status, res = conn.execute_2darray(SQL) for row in res['rows']: - edit_types[row['main_oid']] = row['edit_types'] + edit_types[row['main_oid']] = sorted(row['edit_types']) for column in data['columns']: column_formatter(conn, tid, column['attnum'], column, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py index 019096e06..c5335cc89 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py @@ -227,7 +227,7 @@ class IndexesView(PGChildNodeView, SchemaDiffObjectCompare): }) # Schema Diff: Keys to ignore while comparing - keys_to_ignore = ['oid', 'relowner', 'schema', + keys_to_ignore = ['oid', 'relowner', 'schema', 'indclass', 'indrelid', 'nspname', 'oid-2'] def check_precondition(f): @@ -1068,11 +1068,11 @@ class IndexesView(PGChildNodeView, SchemaDiffObjectCompare): create_req = True if create_req: - diff = self.get_sql_from_index_diff(sid=tgt_params['sid'], - did=tgt_params['did'], - scid=tgt_params['scid'], - tid=tgt_params['tid'], - idx=target['oid'], + diff = self.get_sql_from_index_diff(sid=src_params['sid'], + did=src_params['did'], + scid=src_params['scid'], + tid=src_params['tid'], + idx=source['oid'], diff_schema=target_schema, drop_req=True) else: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/schema_diff_utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/schema_diff_utils.py index 8c12ba4b6..55fe2b693 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/schema_diff_utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/schema_diff_utils.py @@ -22,7 +22,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): table_keys_to_ignore = ['oid', 'schema', 'edit_types', 'attnum', 'col_type', 'references', 'reltuples', 'oid-2', 'rows_cnt', 'seqrelid', 'atttypid', 'elemoid', - 'hastoasttable', 'relhassubclass'] + 'hastoasttable', 'relhassubclass', 'relacl_str'] constraint_keys_to_ignore = ['relname', 'nspname', 'parent_tbl', 'attrelid', 'adrelid', 'fknsp', 'confrelid', @@ -31,7 +31,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): trigger_keys_to_ignore = ['xmin', 'tgrelid', 'tgfoid', 'tfunction', 'tgqual', 'tgconstraint'] - index_keys_to_ignore = ['relowner', 'indrelid'] + index_keys_to_ignore = ['relowner', 'indrelid', 'indclass'] keys_to_ignore = table_keys_to_ignore + constraint_keys_to_ignore \ + trigger_keys_to_ignore + index_keys_to_ignore @@ -51,6 +51,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): 'did': kwargs.get('target_did'), 'scid': kwargs.get('target_scid')} + ignore_whitespaces = kwargs.get('ignore_whitespaces') status, target_schema = self.get_schema(**target_params) if not status: return internal_server_error(errormsg=target_schema) @@ -68,6 +69,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): target_tables, self.node_type, self.blueprint.COLLECTION_LABEL, + ignore_whitespaces, self.keys_to_ignore) def ddl_compare(self, **kwargs): @@ -225,7 +227,8 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): return different def get_sql_from_submodule_diff(self, source_params, target_params, - target_schema, source, target, diff_dict): + target_schema, source, target, diff_dict, + ignore_whitespaces): """ This function returns the DDL/DML statements of the submodules of table based on the comparison status. @@ -236,6 +239,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): :param source: :param target: :param diff_dict: + :param ignore_whitespaces: :return: """ # Get the difference result for source and target columns @@ -250,7 +254,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): target_params['diff_data'] = diff_dict diff = self.get_sql_from_table_diff(**target_params) - ignore_sub_modules = ['column', 'constraints'] + ignore_sub_modules = ['column', 'constraints', 'row_security_policy'] if self.manager.version < 100000: ignore_sub_modules.append('partition') if self.manager.server_type == 'pg' or self.manager.version < 120000: @@ -314,7 +318,8 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare): for key in intersect_keys: # Recursively Compare the two dictionary if not are_dictionaries_identical( - dict1[key], dict2[key], self.keys_to_ignore): + dict1[key], dict2[key], ignore_whitespaces, + self.keys_to_ignore): diff_ddl = module_view.ddl_compare( source_params=source_params, diff --git a/web/pgadmin/static/css/style.css b/web/pgadmin/static/css/style.css index 7ab5ced97..a0a61119d 100644 --- a/web/pgadmin/static/css/style.css +++ b/web/pgadmin/static/css/style.css @@ -16,6 +16,7 @@ @import '~codemirror/addon/scroll/simplescrollbars.css'; @import '~slickgrid/slick.grid.css'; +@import '~slickgrid/slick-default-theme.css'; @import '~slickgrid/css/smoothness/jquery-ui-1.11.3.custom.css'; @import '../vendor/backgrid/backgrid.css'; diff --git a/web/pgadmin/tools/schema_diff/__init__.py b/web/pgadmin/tools/schema_diff/__init__.py index f86ba4937..af676e89a 100644 --- a/web/pgadmin/tools/schema_diff/__init__.py +++ b/web/pgadmin/tools/schema_diff/__init__.py @@ -24,6 +24,7 @@ from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry from pgadmin.tools.schema_diff.model import SchemaDiffModel from config import PG_DEFAULT_DRIVER from pgadmin.utils.driver import get_driver +from pgadmin.utils.preferences import Preferences MODULE_NAME = 'schema_diff' @@ -79,6 +80,16 @@ class SchemaDiffModule(PgAdminModule): 'will be opened in a new browser tab.') ) + self.preference.register( + 'display', 'ignore_whitespaces', + gettext("Ignore whitespaces"), 'boolean', False, + category_label=gettext('Display'), + help_str=gettext('If set to True, then the Schema Diff ' + 'tool ignores the whitespaces while comparing ' + 'the string objects. Whitespace includes space, ' + 'tabs, and CRLF') + ) + blueprint = SchemaDiffModule(MODULE_NAME, __name__, static_url_path='/static') @@ -257,7 +268,7 @@ def servers(): This function will return the list of servers for the specified server id. """ - res = [] + res = {} try: """Return a JSON document listing the server groups for the user""" driver = get_driver(PG_DEFAULT_DRIVER) @@ -269,15 +280,19 @@ def servers(): manager = driver.connection_manager(server.id) conn = manager.connection() connected = conn.connected() - - res.append({ + server_info = { "value": server.id, "label": server.name, "image": server_icon_and_background(connected, manager, server), "_id": server.id, - "connected": connected, - }) + "connected": connected + } + + if server.servers.name in res: + res[server.servers.name].append(server_info) + else: + res[server.servers.name] = [server_info] except Exception as e: app.logger.exception(e) @@ -443,6 +458,9 @@ def compare(trans_id, source_sid, source_did, source_scid, diff_model_obj) try: + pref = Preferences.module('schema_diff') + ignore_whitespaces = pref.preference('ignore_whitespaces').get() + all_registered_nodes = SchemaDiffRegistry.get_registered_nodes() node_percent = round(100 / len(all_registered_nodes)) total_percent = 0 @@ -462,7 +480,8 @@ def compare(trans_id, source_sid, source_did, source_scid, source_scid=source_scid, target_sid=target_sid, target_did=target_did, - target_scid=target_scid) + target_scid=target_scid, + ignore_whitespaces=ignore_whitespaces) if res is not None: comparison_result = comparison_result + res diff --git a/web/pgadmin/tools/schema_diff/compare.py b/web/pgadmin/tools/schema_diff/compare.py index 7459b849b..7feb8e658 100644 --- a/web/pgadmin/tools/schema_diff/compare.py +++ b/web/pgadmin/tools/schema_diff/compare.py @@ -68,6 +68,7 @@ class SchemaDiffObjectCompare: 'scid': kwargs.get('target_scid') } + ignore_whitespaces = kwargs.get('ignore_whitespaces') status, target_schema = self.get_schema(kwargs.get('target_sid'), kwargs.get('target_did'), kwargs.get('target_scid') @@ -88,6 +89,7 @@ class SchemaDiffObjectCompare: target_schema, source, target, self.node_type, gettext(self.blueprint.COLLECTION_LABEL), + ignore_whitespaces, self.keys_to_ignore) def ddl_compare(self, **kwargs): diff --git a/web/pgadmin/tools/schema_diff/directory_compare.py b/web/pgadmin/tools/schema_diff/directory_compare.py index 228a99ae1..0a3738e83 100644 --- a/web/pgadmin/tools/schema_diff/directory_compare.py +++ b/web/pgadmin/tools/schema_diff/directory_compare.py @@ -10,6 +10,7 @@ """Directory comparison""" import copy +import string from pgadmin.tools.schema_diff.model import SchemaDiffModel count = 1 @@ -20,7 +21,7 @@ list_keys_array = ['name', 'colname', 'argid', 'token', 'option', 'conname', def compare_dictionaries(view_object, source_params, target_params, target_schema, source_dict, target_dict, node, - node_label, + node_label, ignore_whitespaces, ignore_keys=None): """ This function will compare the two dictionaries. @@ -33,6 +34,7 @@ def compare_dictionaries(view_object, source_params, target_params, :param target_dict: Second Dictionary :param node: node type :param node_label: node label + :param ignore_whitespaces: If set the True then ignore whitespaces :param ignore_keys: List of keys that will be ignored while comparing :return: """ @@ -139,7 +141,8 @@ def compare_dictionaries(view_object, source_params, target_params, target_object_id = target_dict[key]['oid'] # Recursively Compare the two dictionary - if are_dictionaries_identical(dict1[key], dict2[key], ignore_keys): + if are_dictionaries_identical(dict1[key], dict2[key], + ignore_whitespaces, ignore_keys): identical.append({ 'id': count, 'type': node, @@ -177,7 +180,7 @@ def compare_dictionaries(view_object, source_params, target_params, view_object.get_sql_from_table_diff(**temp_tgt_params) diff_ddl = view_object.get_sql_from_submodule_diff( temp_src_params, temp_tgt_params, target_schema, - dict1[key], dict2[key], diff_dict) + dict1[key], dict2[key], diff_dict, ignore_whitespaces) else: temp_src_params = copy.deepcopy(source_params) temp_tgt_params = copy.deepcopy(target_params) @@ -213,11 +216,13 @@ def compare_dictionaries(view_object, source_params, target_params, return source_only + target_only + different + identical -def are_lists_identical(source_list, target_list, ignore_keys): +def are_lists_identical(source_list, target_list, ignore_whitespaces, + ignore_keys): """ This function is used to compare two list. :param source_list: :param target_list: + :param ignore_whitespaces: ignore whitespaces :param ignore_keys: ignore keys to compare :return: """ @@ -231,6 +236,7 @@ def are_lists_identical(source_list, target_list, ignore_keys): if type(source_list[index]) is dict: if not are_dictionaries_identical(source_list[index], target_list[index], + ignore_whitespaces, ignore_keys): return False else: @@ -239,12 +245,14 @@ def are_lists_identical(source_list, target_list, ignore_keys): return True -def are_dictionaries_identical(source_dict, target_dict, ignore_keys): +def are_dictionaries_identical(source_dict, target_dict, ignore_whitespaces, + ignore_keys): """ This function is used to recursively compare two dictionaries with same keys. :param source_dict: source dict :param target_dict: target dict + :param ignore_whitespaces: If set to True then ignore whitespaces :param ignore_keys: ignore keys to compare :return: """ @@ -275,7 +283,9 @@ def are_dictionaries_identical(source_dict, target_dict, ignore_keys): if type(source_dict[key]) is dict: if not are_dictionaries_identical(source_dict[key], - target_dict[key], ignore_keys): + target_dict[key], + ignore_whitespaces, + ignore_keys): return False elif type(source_dict[key]) is list: # Sort the source and target list on the basis of @@ -284,10 +294,25 @@ def are_dictionaries_identical(source_dict, target_dict, ignore_keys): target_dict[key]) # Compare the source and target lists if not are_lists_identical(source_dict[key], target_dict[key], + ignore_whitespaces, ignore_keys): return False else: - if source_dict[key] != target_dict[key]: + source_value = source_dict[key] + target_value = target_dict[key] + + # If ignore_whitespaces is True then check the source_value and + # target_value if of type string. If the values is of type string + # then using translate function ignore all the whitespaces. + if ignore_whitespaces: + if isinstance(source_value, str): + source_value = source_value.translate( + str.maketrans('', '', string.whitespace)) + if isinstance(target_value, str): + target_value = target_value.translate( + str.maketrans('', '', string.whitespace)) + + if source_value != target_value: return False return True 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 4af8857b2..a8b1006f9 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 @@ -124,16 +124,36 @@ let SchemaDiffSelect2Control = ' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>', ' <%=select2.first_empty ? " " : ""%>', ' <% for (var i=0; i < options.length; i++) {%>', - ' <% var option = options[i]; %>', - ' ', + ' <% if (options[i].group) { %>', + ' <% var group = options[i].group; %>', + ' <% if (options[i].optval) { %> <% var option_length = options[i].optval.length; %>', + ' ', + ' <% for (var subindex=0; subindex < option_length; subindex++) {%>', + ' <% var option = options[i].optval[subindex]; %>', + ' ', + ' <%}%>', + ' ', + ' <%}%>', + ' <%} else {%>', + ' <% var option = options[i]; %>', + ' ', + ' <%}%>', ' <%}%>', ' ', ' <% if (helpMessage && helpMessage.length) { %>', 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 7d10ed42b..7c04d9ce6 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 @@ -556,6 +556,15 @@ export default class SchemaDiffUI { fields: [{ name: 'source_sid', label: false, control: SchemaDiffSelect2Control, + transform: function(data) { + let group_template_options = []; + for (let key in data) { + if (data.hasOwnProperty(key)) { + group_template_options.push({'group': key, 'optval': data[key]}); + } + } + return group_template_options; + }, url: url_for('schema_diff.servers'), select2: { allowClear: true, @@ -636,6 +645,15 @@ export default class SchemaDiffUI { }, { name: 'target_sid', label: false, control: SchemaDiffSelect2Control, + transform: function(data) { + let group_template_options = []; + for (let key in data) { + if (data.hasOwnProperty(key)) { + group_template_options.push({'group': key, 'optval': data[key]}); + } + } + return group_template_options; + }, group: 'target', url: url_for('schema_diff.servers'), select2: {