Skip to content

Commit 45f2e35

Browse files
khushboovashiakshay-joshi
authored andcommitted
Added Schema Diff tool to compare two schemas and generate the difference script. 
Currently supported objects are Table, View, Materialized View, Function and Procedure. Backend comparison of two schemas implemented by: Akshay Joshi Fixes #3452.
1 parent 8b99a33 commit 45f2e35

File tree

87 files changed

+10717
-392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+10717
-392
lines changed

docs/en_US/release_notes_4_18.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
99
New features
1010
************
1111

12+
| `Issue #3452 <https://redmine.postgresql.org/issues/3452>`_ - Added Schema Diff tool to compare two schemas and generate the difference script.
1213
1314
Housekeeping
1415
************

web/pgadmin/browser/server_groups/servers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from pgadmin.utils.driver import get_driver
2929
from pgadmin.utils.master_password import get_crypt_key
3030
from pgadmin.utils.exception import CryptKeyMissing
31+
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
3132
from psycopg2 import Error as psycopg2_Error, OperationalError
3233

3334

@@ -1627,4 +1628,5 @@ def clear_sshtunnel_password(self, gid, sid):
16271628
)
16281629

16291630

1631+
SchemaDiffRegistry(blueprint.node_type, ServerNode)
16301632
ServerNode.register_node_view(blueprint)

web/pgadmin/browser/server_groups/servers/databases/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
make_response as ajax_response, internal_server_error, unauthorized
3131
from pgadmin.utils.driver import get_driver
3232
from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
33+
34+
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
3335
from pgadmin.model import Server
3436

3537

@@ -1111,4 +1113,5 @@ def dependencies(self, gid, sid, did):
11111113
)
11121114

11131115

1116+
SchemaDiffRegistry(blueprint.node_type, DatabaseView)
11141117
DatabaseView.register_node_view(blueprint)

web/pgadmin/browser/server_groups/servers/databases/schemas/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
2424
make_response as ajax_response, gone, bad_request
2525
from pgadmin.utils.driver import get_driver
26+
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
2627

2728
"""
2829
This module is responsible for generating two nodes
@@ -1023,5 +1024,6 @@ def sql(self, gid, sid, did, scid):
10231024
return ajax_response(response=SQL.strip("\n"))
10241025

10251026

1027+
SchemaDiffRegistry(schema_blueprint.node_type, SchemaView)
10261028
SchemaView.register_node_view(schema_blueprint)
10271029
CatalogView.register_node_view(catalog_blueprint)

web/pgadmin/browser/server_groups/servers/databases/schemas/collations/__init__.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
make_response as ajax_response, gone
2626
from pgadmin.utils.compile_template_name import compile_template_path
2727
from pgadmin.utils.driver import get_driver
28+
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
29+
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
2830

2931
# If we are in Python3
3032
if not IS_PY2:
@@ -92,7 +94,7 @@ def node_inode(self):
9294
blueprint = CollationModule(__name__)
9395

9496

95-
class CollationView(PGChildNodeView):
97+
class CollationView(PGChildNodeView, SchemaDiffObjectCompare):
9698
"""
9799
This class is responsible for generating routes for Collation node
98100
@@ -144,6 +146,10 @@ class CollationView(PGChildNodeView):
144146
* dependent(gid, sid, did, scid):
145147
- This function will generate dependent list to show it in dependent
146148
pane for the selected Collation node.
149+
150+
* compare(**kwargs):
151+
- This function will compare the collation nodes from two different
152+
schemas.
147153
"""
148154

149155
node_type = blueprint.node_type
@@ -172,7 +178,8 @@ class CollationView(PGChildNodeView):
172178
'dependency': [{'get': 'dependencies'}],
173179
'dependent': [{'get': 'dependents'}],
174180
'get_collations': [{'get': 'get_collation'},
175-
{'get': 'get_collation'}]
181+
{'get': 'get_collation'}],
182+
'compare': [{'get': 'compare'}, {'get': 'compare'}]
176183
})
177184

178185
def check_precondition(f):
@@ -318,23 +325,36 @@ def properties(self, gid, sid, did, scid, coid):
318325
JSON of selected collation node
319326
"""
320327

328+
status, res = self._fetch_properties(scid, coid)
329+
if not status:
330+
return res
331+
332+
return ajax_response(
333+
response=res,
334+
status=200
335+
)
336+
337+
def _fetch_properties(self, scid, coid):
338+
"""
339+
This function fetch the properties for the specified object.
340+
341+
:param scid: Schema ID
342+
:param coid: Collation ID
343+
"""
344+
321345
SQL = render_template("/".join([self.template_path,
322346
'properties.sql']),
323347
scid=scid, coid=coid)
324348
status, res = self.conn.execute_dict(SQL)
325349

326350
if not status:
327-
return internal_server_error(errormsg=res)
351+
return False, internal_server_error(errormsg=res)
328352

329353
if len(res['rows']) == 0:
330-
return gone(
331-
gettext("Could not find the collation object in the database.")
332-
)
354+
return False, gone(gettext("Could not find the collation "
355+
"object in the database."))
333356

334-
return ajax_response(
335-
response=res['rows'][0],
336-
status=200
337-
)
357+
return True, res['rows'][0]
338358

339359
@check_precondition
340360
def get_collation(self, gid, sid, did, scid, coid=None):
@@ -748,5 +768,30 @@ def dependencies(self, gid, sid, did, scid, coid):
748768
status=200
749769
)
750770

771+
@check_precondition
772+
def fetch_objects_to_compare(self, sid, did, scid):
773+
"""
774+
This function will fetch the list of all the collations for
775+
specified schema id.
776+
777+
:param sid: Server Id
778+
:param did: Database Id
779+
:param scid: Schema Id
780+
:return:
781+
"""
782+
res = dict()
783+
SQL = render_template("/".join([self.template_path,
784+
'nodes.sql']), scid=scid)
785+
status, rset = self.conn.execute_2darray(SQL)
786+
if not status:
787+
return internal_server_error(errormsg=res)
788+
789+
for row in rset['rows']:
790+
status, data = self._fetch_properties(scid, row['oid'])
791+
if status:
792+
res[row['name']] = data
793+
794+
return res
795+
751796

752797
CollationView.register_node_view(blueprint)

web/pgadmin/browser/server_groups/servers/databases/schemas/domains/__init__.py

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
make_response as ajax_response, gone
2828
from pgadmin.utils.compile_template_name import compile_template_path
2929
from pgadmin.utils.driver import get_driver
30+
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
31+
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
3032

3133
# If we are in Python3
3234
if not IS_PY2:
@@ -79,7 +81,7 @@ def script_load(self):
7981
blueprint = DomainModule(__name__)
8082

8183

82-
class DomainView(PGChildNodeView, DataTypeReader):
84+
class DomainView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
8385
"""
8486
class DomainView
8587
@@ -138,6 +140,10 @@ class DomainView
138140
139141
* types(gid, sid, did, scid, fnid=None):
140142
- Returns Data Types.
143+
144+
* compare(**kwargs):
145+
- This function will compare the domain nodes from two different
146+
schemas.
141147
"""
142148

143149
node_type = blueprint.node_type
@@ -169,7 +175,8 @@ class DomainView
169175
'get_collations': [
170176
{'get': 'get_collations'},
171177
{'get': 'get_collations'}
172-
]
178+
],
179+
'compare': [{'get': 'compare'}, {'get': 'compare'}]
173180
})
174181

175182
def validate_request(f):
@@ -369,15 +376,31 @@ def properties(self, gid, sid, did, scid, doid):
369376
scid: Schema Id
370377
doid: Domain Id
371378
"""
379+
status, res = self._fetch_properties(did, scid, doid)
380+
if not status:
381+
return res
382+
383+
return ajax_response(
384+
response=res,
385+
status=200
386+
)
372387

388+
def _fetch_properties(self, did, scid, doid):
389+
"""
390+
This function is used to fecth the properties of specified object.
391+
:param did:
392+
:param scid:
393+
:param doid:
394+
:return:
395+
"""
373396
SQL = render_template("/".join([self.template_path, 'properties.sql']),
374397
scid=scid, doid=doid)
375398
status, res = self.conn.execute_dict(SQL)
376399
if not status:
377-
return internal_server_error(errormsg=res)
400+
return False, internal_server_error(errormsg=res)
378401

379402
if len(res['rows']) == 0:
380-
return gone(gettext("""
403+
return False, gone(gettext("""
381404
Could not find the domain in the database.
382405
It may have been removed by another user or moved to another schema.
383406
"""))
@@ -393,7 +416,7 @@ def properties(self, gid, sid, did, scid, doid):
393416
doid=doid)
394417
status, res = self.conn.execute_dict(SQL)
395418
if not status:
396-
return internal_server_error(errormsg=res)
419+
return False, internal_server_error(errormsg=res)
397420

398421
data['constraints'] = res['rows']
399422

@@ -406,10 +429,7 @@ def properties(self, gid, sid, did, scid, doid):
406429
if doid <= self.manager.db_info[did]['datlastsysoid']:
407430
data['sysdomain'] = True
408431

409-
return ajax_response(
410-
response=data,
411-
status=200
412-
)
432+
return True, data
413433

414434
def _parse_type(self, basetype):
415435
"""
@@ -664,7 +684,7 @@ def update(self, gid, sid, did, scid, doid):
664684
)
665685

666686
@check_precondition
667-
def sql(self, gid, sid, did, scid, doid=None):
687+
def sql(self, gid, sid, did, scid, doid=None, return_ajax_response=True):
668688
"""
669689
Returns the SQL for the Domain object.
670690
@@ -674,6 +694,7 @@ def sql(self, gid, sid, did, scid, doid=None):
674694
did: Database Id
675695
scid: Schema Id
676696
doid: Domain Id
697+
return_ajax_response:
677698
"""
678699

679700
SQL = render_template("/".join([self.template_path,
@@ -716,6 +737,9 @@ def sql(self, gid, sid, did, scid, doid=None):
716737
""".format(self.qtIdent(self.conn, data['basensp'], data['name']))
717738
SQL = sql_header + SQL
718739

740+
if not return_ajax_response:
741+
return SQL.strip('\n')
742+
719743
return ajax_response(response=SQL.strip('\n'))
720744

721745
@check_precondition
@@ -846,5 +870,40 @@ def dependencies(self, gid, sid, did, scid, doid):
846870
status=200
847871
)
848872

873+
@check_precondition
874+
def fetch_objects_to_compare(self, sid, did, scid):
875+
"""
876+
This function will fetch the list of all the domains for
877+
specified schema id.
878+
879+
:param sid: Server Id
880+
:param did: Database Id
881+
:param scid: Schema Id
882+
:return:
883+
"""
884+
res = dict()
885+
SQL = render_template("/".join([self.template_path,
886+
'node.sql']), scid=scid)
887+
status, rset = self.conn.execute_2darray(SQL)
888+
if not status:
889+
return internal_server_error(errormsg=res)
890+
891+
for row in rset['rows']:
892+
status, data = self._fetch_properties(did, scid, row['oid'])
893+
894+
if status:
895+
if 'constraints' in data and len(data['constraints']) > 0:
896+
for item in data['constraints']:
897+
# Remove keys that should not be the part
898+
# of comparision.
899+
if 'conoid' in item:
900+
item.pop('conoid')
901+
if 'nspname' in item:
902+
item.pop('nspname')
903+
904+
res[row['name']] = data
905+
906+
return res
907+
849908

850909
DomainView.register_node_view(blueprint)

web/pgadmin/browser/server_groups/servers/databases/schemas/domains/templates/domains/sql/9.2_plus/get_constraints.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ JOIN
88
JOIN
99
pg_namespace nl ON nl.oid=typnamespace
1010
LEFT OUTER JOIN
11-
pg_description des ON (des.objoid=t.oid AND des.classoid='pg_constraint'::regclass)
11+
pg_description des ON (des.objoid=c.oid AND des.classoid='pg_constraint'::regclass)
1212
WHERE
1313
contype = 'c' AND contypid = {{doid}}::oid
1414
ORDER BY

web/pgadmin/browser/server_groups/servers/databases/schemas/domains/templates/domains/sql/default/get_constraints.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ JOIN
88
JOIN
99
pg_namespace nl ON nl.oid=typnamespace
1010
LEFT OUTER JOIN
11-
pg_description des ON (des.objoid=t.oid AND des.classoid='pg_constraint'::regclass)
11+
pg_description des ON (des.objoid=c.oid AND des.classoid='pg_constraint'::regclass)
1212
WHERE
1313
contype = 'c'
1414
AND contypid = {{doid}}::oid

0 commit comments

Comments
 (0)