1) Added option to ignore the whitespaces while comparing objects in schema diff. Fixes #5468

2) Added server group name while selecting servers in schema diff. Fixes #5500
3) Fixed an issue where two identical tables showing different by schema diff tool. Fixes #5584
pull/33/head
Akshay Joshi 2020-06-17 16:27:51 +05:30
parent 9f5e8962b5
commit cb268075c2
12 changed files with 132 additions and 36 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -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.

View File

@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #5468 <https://redmine.postgresql.org/issues/5468>`_ - Added option to ignore the whitespaces while comparing objects in schema diff.
| `Issue #5500 <https://redmine.postgresql.org/issues/5500>`_ - Added server group name while selecting servers in schema diff.
| `Issue #5516 <https://redmine.postgresql.org/issues/5516>`_ - Added support of Row Security Policies.
| `Issue #5576 <https://redmine.postgresql.org/issues/5576>`_ - Improve error messaging if the storage and log directories cannot be created.
@ -29,4 +31,5 @@ Bug fixes
| `Issue #5507 <https://redmine.postgresql.org/issues/5507>`_ - Fixed connection and version number detection issue when the database server is upgraded.
| `Issue #5521 <https://redmine.postgresql.org/issues/5521>`_ - Fixed an issue when dumping servers from a desktop pgAdmin app by providing an option '--sqlite-path'.
| `Issue #5539 <https://redmine.postgresql.org/issues/5539>`_ - Fixed typo in exception keyword.
| `Issue #5584 <https://redmine.postgresql.org/issues/5584>`_ - Fixed an issue where two identical tables showing different by schema diff tool.
| `Issue #5592 <https://redmine.postgresql.org/issues/5592>`_ - Ensure that pgadmin should be able to connect to the server which has password more than 1000 characters.

View File

@ -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,

View File

@ -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:

View File

@ -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,

View File

@ -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';

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -124,16 +124,36 @@ let SchemaDiffSelect2Control =
' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>',
' <%=select2.first_empty ? " <option></option>" : ""%>',
' <% for (var i=0; i < options.length; i++) {%>',
' <% var option = options[i]; %>',
' <option ',
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
' <% if (option.connected) { %> data-connected=connected <%}%>',
' value=<%- formatter.fromRaw(option.value) %>',
' <% if (option.selected) {%>selected="selected"<%} else {%>',
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
' <%}%>',
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
' <% if (options[i].group) { %>',
' <% var group = options[i].group; %>',
' <% if (options[i].optval) { %> <% var option_length = options[i].optval.length; %>',
' <optgroup label="<%=group%>">',
' <% for (var subindex=0; subindex < option_length; subindex++) {%>',
' <% var option = options[i].optval[subindex]; %>',
' <option ',
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
' <% if (option.connected) { %> data-connected=connected <%}%>',
' value=<%- formatter.fromRaw(option.value) %>',
' <% if (option.selected) {%>selected="selected"<%} else {%>',
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
' <%}%>',
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
' <%}%>',
' </optgroup>',
' <%}%>',
' <%} else {%>',
' <% var option = options[i]; %>',
' <option ',
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
' <% if (option.connected) { %> data-connected=connected <%}%>',
' value=<%- formatter.fromRaw(option.value) %>',
' <% if (option.selected) {%>selected="selected"<%} else {%>',
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
' <%}%>',
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
' <%}%>',
' <%}%>',
' </select>',
' <% if (helpMessage && helpMessage.length) { %>',

View File

@ -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: {