codekingpro/portable-devtools
115k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""A blueprint module implementing the schema_diff frame."""11import json12import pickle13import secrets14import copy15 16from flask import Response, session, url_for, request17from flask import render_template, current_app as app18from flask_security import current_user19from pgadmin.user_login_check import pga_login_required20from flask_babel import gettext21from pgadmin.utils import PgAdminModule22from pgadmin.utils.ajax import make_json_response, bad_request, \23 make_response as ajax_response, internal_server_error24from pgadmin.model import Server, SharedServer25from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry26from pgadmin.tools.schema_diff.model import SchemaDiffModel27from config import PG_DEFAULT_DRIVER28from pgadmin.utils.driver import get_driver29from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS,\30 ERROR_MSG_TRANS_ID_NOT_FOUND31from sqlalchemy import or_32from pgadmin.authenticate import socket_login_required33from pgadmin import socketio34 35MODULE_NAME = 'schema_diff'36COMPARE_MSG = gettext("Comparing objects...")37SOCKETIO_NAMESPACE = '/{0}'.format(MODULE_NAME)38 39 40class SchemaDiffModule(PgAdminModule):41 """42 class SchemaDiffModule(PgAdminModule)43 44 A module class for Schema Diff derived from PgAdminModule.45 """46 47 LABEL = gettext("Schema Diff")48 49 def get_own_menuitems(self):50 return {}51 52 def get_exposed_url_endpoints(self):53 """54 Returns:55 list: URL endpoints for Schema Diff module56 """57 return [58 'schema_diff.initialize',59 'schema_diff.panel',60 'schema_diff.servers',61 'schema_diff.databases',62 'schema_diff.schemas',63 'schema_diff.ddl_compare',64 'schema_diff.connect_server',65 'schema_diff.connect_database',66 'schema_diff.get_server',67 'schema_diff.close'68 ]69 70 def register_preferences(self):71 72 self.preference.register(73 'display', 'ignore_whitespaces',74 gettext("Ignore Whitespace"), 'boolean', False,75 category_label=PREF_LABEL_DISPLAY,76 help_str=gettext('Set ignore whitespace on or off by default in '77 'the drop-down menu near the Compare button in '78 'the Schema Diff tab.')79 )80 81 self.preference.register(82 'display', 'ignore_owner',83 gettext("Ignore Owner"), 'boolean', False,84 category_label=PREF_LABEL_DISPLAY,85 help_str=gettext('Set ignore owner on or off by default in the '86 'drop-down menu near the Compare button in the '87 'Schema Diff tab.')88 )89 90 self.preference.register(91 'display', 'ignore_tablespace',92 gettext("Ignore Tablespace"), 'boolean', False,93 category_label=PREF_LABEL_DISPLAY,94 help_str=gettext('Set ignore tablespace on or off by default in '95 'the drop-down menu near the Compare button in '96 'the Schema Diff tab.')97 )98 99 self.preference.register(100 'display', 'ignore_grants',101 gettext("Ignore Grants/Revoke"), 'boolean', False,102 category_label=PREF_LABEL_DISPLAY,103 help_str=gettext('Set ignore grants/revoke on or off by default '104 'in the drop-down menu near the Compare button '105 'in the Schema Diff tab.')106 )107 108 109blueprint = SchemaDiffModule(MODULE_NAME, __name__, static_url_path='/static')110 111 112@blueprint.route("/")113@pga_login_required114def index():115 return bad_request(116 errormsg=gettext('This URL cannot be requested directly.')117 )118 119 120@blueprint.route(121 '/panel/<int:trans_id>/<path:editor_title>',122 methods=["GET"],123 endpoint='panel'124)125def panel(trans_id, editor_title):126 """127 This method calls index.html to render the schema diff.128 129 Args:130 editor_title: Title of the editor131 """132 # If title has slash(es) in it then replace it133 if request.args and request.args['fslashes'] != '':134 try:135 fslashes_list = request.args['fslashes'].split(',')136 for idx in fslashes_list:137 idx = int(idx)138 editor_title = editor_title[:idx] + '/' + editor_title[idx:]139 except IndexError as e:140 app.logger.exception(e)141 142 return render_template(143 "schema_diff/index.html",144 _=gettext,145 trans_id=trans_id,146 editor_title=editor_title,147 )148 149 150def check_transaction_status(trans_id):151 """152 This function is used to check the transaction id153 is available in the session object.154 155 Args:156 trans_id:157 """158 159 if 'schemaDiff' not in session:160 return False, ERROR_MSG_TRANS_ID_NOT_FOUND, None, None161 162 schema_diff_data = session['schemaDiff']163 164 # Return from the function if transaction id not found165 if str(trans_id) not in schema_diff_data:166 return False, ERROR_MSG_TRANS_ID_NOT_FOUND, None, None167 168 # Fetch the object for the specified transaction id.169 # Use pickle.loads function to get the model object170 session_obj = schema_diff_data[str(trans_id)]171 diff_model_obj = pickle.loads(session_obj['diff_model_obj'])172 173 return True, None, diff_model_obj, session_obj174 175 176def update_session_diff_transaction(trans_id, session_obj, diff_model_obj):177 """178 This function is used to update the diff model into the session.179 :param trans_id:180 :param session_obj:181 :param diff_model_obj:182 :return:183 """184 session_obj['diff_model_obj'] = pickle.dumps(diff_model_obj, -1)185 186 if 'schemaDiff' in session:187 schema_diff_data = session['schemaDiff']188 schema_diff_data[str(trans_id)] = session_obj189 session['schemaDiff'] = schema_diff_data190 191 192@blueprint.route(193 '/initialize',194 methods=["GET"],195 endpoint="initialize"196)197@pga_login_required198def initialize():199 """200 This function will initialize the schema diff and return the list201 of all the server's.202 """203 trans_id = None204 try:205 # Create a unique id for the transaction206 trans_id = str(secrets.choice(range(1, 9999999)))207 208 if 'schemaDiff' not in session:209 schema_diff_data = dict()210 else:211 schema_diff_data = session['schemaDiff']212 213 # Use pickle to store the Schema Diff Model which will be used214 # later by the diff module.215 schema_diff_data[trans_id] = {216 'diff_model_obj': pickle.dumps(SchemaDiffModel(), -1)217 }218 219 # Store the schema diff dictionary into the session variable220 session['schemaDiff'] = schema_diff_data221 222 except Exception as e:223 app.logger.exception(e)224 225 return make_json_response(226 data={'schemaDiffTransId': trans_id})227 228 229@blueprint.route('/close/<int:trans_id>',230 methods=["DELETE"],231 endpoint='close')232def close(trans_id):233 """234 Remove the session details for the particular transaction id.235 236 Args:237 trans_id: unique transaction id238 """239 if 'schemaDiff' not in session:240 return make_json_response(data={'status': True})241 242 schema_diff_data = session['schemaDiff']243 244 # Return from the function if transaction id not found245 if str(trans_id) not in schema_diff_data:246 return make_json_response(data={'status': True})247 248 try:249 # Remove the information of unique transaction id from the250 # session variable.251 schema_diff_data.pop(str(trans_id), None)252 session['schemaDiff'] = schema_diff_data253 except Exception as e:254 app.logger.error(e)255 return internal_server_error(errormsg=str(e))256 257 return make_json_response(data={'status': True})258 259 260@blueprint.route(261 '/servers',262 methods=["GET"],263 endpoint="servers"264)265@pga_login_required266def servers():267 """268 This function will return the list of servers for the specified269 server id.270 """271 res = {}272 auto_detected_server = None273 try:274 """Return a JSON document listing the server groups for the user"""275 driver = get_driver(PG_DEFAULT_DRIVER)276 277 from pgadmin.browser.server_groups.servers import\278 server_icon_and_background279 280 for server in Server.query.filter(281 or_(Server.user_id == current_user.id, Server.shared)):282 283 shared_server = SharedServer.query.filter_by(284 name=server.name, user_id=current_user.id,285 servergroup_id=server.servergroup_id).first()286 287 if server.discovery_id:288 auto_detected_server = server.name289 290 if shared_server and shared_server.name == auto_detected_server:291 continue292 293 manager = driver.connection_manager(server.id)294 conn = manager.connection()295 connected = conn.connected()296 server_info = {297 "value": server.id,298 "label": server.name,299 "image": server_icon_and_background(connected, manager,300 server),301 "_id": server.id,302 "connected": connected303 }304 305 if server.servers.name in res:306 res[server.servers.name].append(server_info)307 else:308 res[server.servers.name] = [server_info]309 310 except Exception as e:311 app.logger.exception(e)312 313 return make_json_response(data=res)314 315 316@blueprint.route(317 '/get_server/<int:sid>/<int:did>',318 methods=["GET"],319 endpoint="get_server"320)321@pga_login_required322def get_server(sid, did):323 """324 This function will return the server details for the specified325 server id.326 """327 res = []328 try:329 """Return a JSON document listing the server groups for the user"""330 driver = get_driver(PG_DEFAULT_DRIVER)331 332 server = Server.query.filter_by(id=sid).first()333 manager = driver.connection_manager(sid)334 conn = manager.connection(did=did)335 connected = conn.connected()336 337 res = {338 "sid": sid,339 "name": server.name,340 "user": server.username,341 "gid": server.servergroup_id,342 "type": manager.server_type,343 "connected": connected,344 "database": conn.db345 }346 347 except Exception as e:348 app.logger.exception(e)349 350 return make_json_response(data=res)351 352 353@blueprint.route(354 '/server/connect/<int:sid>',355 methods=["POST"],356 endpoint="connect_server"357)358@pga_login_required359def connect_server(sid):360 # Check if server is already connected then no need to reconnect again.361 driver = get_driver(PG_DEFAULT_DRIVER)362 manager = driver.connection_manager(sid)363 conn = manager.connection()364 if conn.connected():365 return make_json_response(366 success=1,367 info=gettext("Server connected."),368 data={}369 )370 371 server = Server.query.filter_by(id=sid).first()372 view = SchemaDiffRegistry.get_node_view('server')373 return view.connect(server.servergroup_id, sid)374 375 376@blueprint.route(377 '/database/connect/<int:sid>/<int:did>',378 methods=["POST"],379 endpoint="connect_database"380)381@pga_login_required382def connect_database(sid, did):383 server = Server.query.filter_by(id=sid).first()384 view = SchemaDiffRegistry.get_node_view('database')385 return view.connect(server.servergroup_id, sid, did)386 387 388@blueprint.route(389 '/databases/<int:sid>',390 methods=["GET"],391 endpoint="databases"392)393@pga_login_required394def databases(sid):395 """396 This function will return the list of databases for the specified397 server id.398 """399 res = []400 try:401 view = SchemaDiffRegistry.get_node_view('database')402 403 server = Server.query.filter_by(id=sid).first()404 response = view.nodes(gid=server.servergroup_id, sid=sid,405 is_schema_diff=True)406 databases = json.loads(response.data)['data']407 for db in databases:408 res.append({409 "value": db['_id'],410 "label": db['label'],411 "_id": db['_id'],412 "connected": db['connected'],413 "allowConn": db['allowConn'],414 "image": db['icon'],415 "canDisconn": db['canDisconn'],416 "is_maintenance_db": db['label'] == server.maintenance_db417 })418 419 except Exception as e:420 app.logger.exception(e)421 422 return make_json_response(data=res)423 424 425@blueprint.route(426 '/schemas/<int:sid>/<int:did>',427 methods=["GET"],428 endpoint="schemas"429)430@pga_login_required431def schemas(sid, did):432 """433 This function will return the list of schemas for the specified434 server id and database id.435 """436 res = []437 try:438 schemas = get_schemas(sid, did)439 if schemas is not None:440 for sch in schemas:441 res.append({442 "value": sch['_id'],443 "label": sch['label'],444 "_id": sch['_id'],445 "image": sch['icon'],446 })447 except Exception as e:448 app.logger.exception(e)449 450 return make_json_response(data=res)451 452 453@socketio.on('compare_database', namespace=SOCKETIO_NAMESPACE)454@socket_login_required455def compare_database(params):456 """457 This function will compare the two databases.458 """459 # Check the pre validation before compare460 status, error_msg, diff_model_obj, session_obj = \461 compare_pre_validation(params['trans_id'], params['source_sid'],462 params['target_sid'])463 if not status:464 socketio.emit('compare_database_failed',465 error_msg.json if isinstance(466 error_msg, Response) else error_msg,467 namespace=SOCKETIO_NAMESPACE, to=request.sid)468 return error_msg469 470 comparison_result = []471 472 socketio.emit('compare_status', {'diff_percentage': 0,473 'compare_msg': COMPARE_MSG}, namespace=SOCKETIO_NAMESPACE,474 to=request.sid)475 update_session_diff_transaction(params['trans_id'], session_obj,476 diff_model_obj)477 478 try:479 ignore_owner = bool(params['ignore_owner'])480 ignore_whitespaces = bool(params['ignore_whitespaces'])481 ignore_tablespace = bool(params['ignore_tablespace'])482 ignore_grants = bool(params['ignore_grants'])483 484 # Fetch all the schemas of source and target database485 # Compare them and get the status.486 schema_result = \487 fetch_compare_schemas(params['source_sid'], params['source_did'],488 params['target_sid'], params['target_did'])489 490 total_schema = len(schema_result['source_only']) + len(491 schema_result['target_only']) + len(492 schema_result['in_both_database'])493 494 node_percent = 0495 if total_schema > 0:496 node_percent = round(100 / (total_schema * len(497 SchemaDiffRegistry.get_registered_nodes())), 2)498 total_percent = 0499 500 # Compare Database objects501 comparison_schema_result, total_percent = \502 compare_database_objects(503 trans_id=params['trans_id'], session_obj=session_obj,504 source_sid=params['source_sid'],505 source_did=params['source_did'],506 target_sid=params['target_sid'],507 target_did=params['target_did'],508 diff_model_obj=diff_model_obj, total_percent=total_percent,509 node_percent=node_percent, ignore_owner=ignore_owner,510 ignore_whitespaces=ignore_whitespaces,511 ignore_tablespace=ignore_tablespace,512 ignore_grants=ignore_grants)513 comparison_result = \514 comparison_result + comparison_schema_result515 516 # Compare Schema objects517 if 'source_only' in schema_result and \518 len(schema_result['source_only']) > 0:519 for item in schema_result['source_only']:520 comparison_schema_result, total_percent = \521 compare_schema_objects(522 trans_id=params['trans_id'], session_obj=session_obj,523 source_sid=params['source_sid'],524 source_did=params['source_did'],525 source_scid=item['scid'],526 target_sid=params['target_sid'],527 target_did=params['target_did'], target_scid=None,528 schema_name=item['schema_name'],529 diff_model_obj=diff_model_obj,530 total_percent=total_percent,531 node_percent=node_percent,532 is_schema_source_only=True,533 ignore_owner=ignore_owner,534 ignore_whitespaces=ignore_whitespaces,535 ignore_tablespace=ignore_tablespace,536 ignore_grants=ignore_grants)537 538 comparison_result = \539 comparison_result + comparison_schema_result540 541 if 'target_only' in schema_result and \542 len(schema_result['target_only']) > 0:543 for item in schema_result['target_only']:544 comparison_schema_result, total_percent = \545 compare_schema_objects(546 trans_id=params['trans_id'], session_obj=session_obj,547 source_sid=params['source_sid'],548 source_did=params['source_did'],549 source_scid=None, target_sid=params['target_sid'],550 target_did=params['target_did'],551 target_scid=item['scid'],552 schema_name=item['schema_name'],553 diff_model_obj=diff_model_obj,554 total_percent=total_percent,555 node_percent=node_percent,556 ignore_owner=ignore_owner,557 ignore_whitespaces=ignore_whitespaces,558 ignore_tablespace=ignore_tablespace,559 ignore_grants=ignore_grants)560 561 comparison_result = \562 comparison_result + comparison_schema_result563 564 # Compare the two schema present in both the databases565 if 'in_both_database' in schema_result and \566 len(schema_result['in_both_database']) > 0:567 for item in schema_result['in_both_database']:568 comparison_schema_result, total_percent = \569 compare_schema_objects(570 trans_id=params['trans_id'], session_obj=session_obj,571 source_sid=params['source_sid'],572 source_did=params['source_did'],573 source_scid=item['src_scid'],574 target_sid=params['target_sid'],575 target_did=params['target_did'],576 target_scid=item['tar_scid'],577 schema_name=item['schema_name'],578 diff_model_obj=diff_model_obj,579 total_percent=total_percent,580 node_percent=node_percent,581 ignore_owner=ignore_owner,582 ignore_whitespaces=ignore_whitespaces,583 ignore_tablespace=ignore_tablespace,584 ignore_grants=ignore_grants)585 586 comparison_result = \587 comparison_result + comparison_schema_result588 589 # Update the message and total percentage done in session object590 update_session_diff_transaction(params['trans_id'], session_obj,591 diff_model_obj)592 593 except Exception as e:594 app.logger.exception(e)595 socketio.emit('compare_database_failed', str(e),596 namespace=SOCKETIO_NAMESPACE, to=request.sid)597 598 socketio.emit('compare_database_success', comparison_result,599 namespace=SOCKETIO_NAMESPACE, to=request.sid)600 601 602@socketio.on('compare_schema', namespace=SOCKETIO_NAMESPACE)603@socket_login_required604def compare_schema(params):605 """606 This function will compare the two schema.607 """608 # Check the pre validation before compare609 status, error_msg, diff_model_obj, session_obj = \610 compare_pre_validation(params['trans_id'], params['source_sid'],611 params['target_sid'])612 if not status:613 socketio.emit('compare_schema_failed',614 error_msg.json if isinstance(615 error_msg, Response) else error_msg,616 namespace=SOCKETIO_NAMESPACE, to=request.sid)617 return error_msg618 619 comparison_result = []620 621 update_session_diff_transaction(params['trans_id'], session_obj,622 diff_model_obj)623 try:624 ignore_owner = bool(params['ignore_owner'])625 ignore_whitespaces = bool(params['ignore_whitespaces'])626 ignore_tablespace = bool(params['ignore_tablespace'])627 ignore_grants = bool(params['ignore_grants'])628 all_registered_nodes = SchemaDiffRegistry.get_registered_nodes()629 node_percent = round(100 / len(all_registered_nodes), 2)630 total_percent = 0631 632 comparison_schema_result, total_percent = \633 compare_schema_objects(634 trans_id=params['trans_id'], session_obj=session_obj,635 source_sid=params['source_sid'],636 source_did=params['source_did'],637 source_scid=params['source_scid'],638 target_sid=params['target_sid'],639 target_did=params['target_did'],640 target_scid=params['target_scid'],641 schema_name=gettext('Schema Objects'),642 diff_model_obj=diff_model_obj,643 total_percent=total_percent,644 node_percent=node_percent,645 ignore_owner=ignore_owner,646 ignore_whitespaces=ignore_whitespaces,647 ignore_tablespace=ignore_tablespace,648 ignore_grants=ignore_grants)649 650 comparison_result = \651 comparison_result + comparison_schema_result652 653 # Update the message and total percentage done in session object654 update_session_diff_transaction(params['trans_id'], session_obj,655 diff_model_obj)656 657 except Exception as e:658 app.logger.exception(e)659 socketio.emit('compare_schema_failed', str(e),660 namespace=SOCKETIO_NAMESPACE, to=request.sid)661 socketio.emit('compare_schema_success', comparison_result,662 namespace=SOCKETIO_NAMESPACE, to=request.sid)663 664 665@blueprint.route(666 '/ddl_compare/<int:trans_id>/<int:source_sid>/<int:source_did>/'667 '<int:source_scid>/<int:target_sid>/<int:target_did>/<int:target_scid>/'668 '<int:source_oid>/<int:target_oid>/<node_type>/<comp_status>/',669 methods=["GET"],670 endpoint="ddl_compare"671)672@pga_login_required673def ddl_compare(trans_id, source_sid, source_did, source_scid,674 target_sid, target_did, target_scid, source_oid,675 target_oid, node_type, comp_status):676 """677 This function is used to compare the specified object and return the678 DDL comparison.679 """680 # Check the transaction and connection status681 _, error_msg, _, _ = \682 check_transaction_status(trans_id)683 684 if error_msg == ERROR_MSG_TRANS_ID_NOT_FOUND:685 return make_json_response(success=0, errormsg=error_msg, status=404)686 687 view = SchemaDiffRegistry.get_node_view(node_type)688 if view and hasattr(view, 'ddl_compare'):689 sql = view.ddl_compare(source_sid=source_sid, source_did=source_did,690 source_scid=source_scid, target_sid=target_sid,691 target_did=target_did, target_scid=target_scid,692 source_oid=source_oid, target_oid=target_oid,693 comp_status=comp_status)694 return ajax_response(695 status=200,696 response={'source_ddl': sql['source_ddl'],697 'target_ddl': sql['target_ddl'],698 'diff_ddl': sql['diff_ddl']}699 )700 701 msg = gettext('Selected object is not supported for DDL comparison.')702 703 return ajax_response(704 status=200,705 response={'source_ddl': msg,706 'target_ddl': msg,707 'diff_ddl': msg708 }709 )710 711 712def check_version_compatibility(sid, tid):713 """Check the version compatibility of source and target servers."""714 715 driver = get_driver(PG_DEFAULT_DRIVER)716 src_server = Server.query.filter_by(id=sid).first()717 src_manager = driver.connection_manager(src_server.id)718 src_conn = src_manager.connection()719 720 tar_server = Server.query.filter_by(id=tid).first()721 tar_manager = driver.connection_manager(tar_server.id)722 target_conn = tar_manager.connection()723 724 if not (src_conn.connected() and target_conn.connected()):725 return False, gettext('Server(s) disconnected.')726 727 if src_manager.server_type != tar_manager.server_type:728 return False, gettext('Schema diff does not support the comparison '729 'between Postgres Server and EDB Postgres '730 'Advanced Server.')731 732 def get_round_val(x):733 if x < 100000:734 return x + 100 - x % 100735 else:736 return x + 10000 - x % 10000737 738 if get_round_val(src_manager.version) == \739 get_round_val(tar_manager.version):740 return True, None741 742 return False, gettext('Source and Target database server must be of '743 'the same major version.')744 745 746def get_schemas(sid, did):747 """748 This function will return the list of schemas for the specified749 server id and database id.750 """751 try:752 view = SchemaDiffRegistry.get_node_view('schema')753 server = Server.query.filter_by(id=sid).first()754 response = view.nodes(gid=server.servergroup_id, sid=sid, did=did,755 is_schema_diff=True)756 schemas = json.loads(response.data)['data']757 return schemas758 except Exception as e:759 app.logger.exception(e)760 761 return None762 763 764def compare_database_objects(**kwargs):765 """766 This function is used to compare the specified schema and their children.767 768 :param kwargs:769 :return:770 """771 trans_id = kwargs.get('trans_id')772 session_obj = kwargs.get('session_obj')773 source_sid = kwargs.get('source_sid')774 source_did = kwargs.get('source_did')775 target_sid = kwargs.get('target_sid')776 target_did = kwargs.get('target_did')777 diff_model_obj = kwargs.get('diff_model_obj')778 total_percent = kwargs.get('total_percent')779 node_percent = kwargs.get('node_percent')780 ignore_owner = kwargs.get('ignore_owner')781 ignore_whitespaces = kwargs.get('ignore_whitespaces')782 ignore_tablespace = kwargs.get('ignore_tablespace')783 ignore_grants = kwargs.get('ignore_grants')784 comparison_result = []785 786 all_registered_nodes = SchemaDiffRegistry.get_registered_nodes(None,787 'Database')788 for node_name, node_view in all_registered_nodes.items():789 view = SchemaDiffRegistry.get_node_view(node_name)790 if hasattr(view, 'compare'):791 msg = gettext('Comparing {0}'). \792 format(gettext(view.blueprint.collection_label))793 app.logger.debug(msg)794 socketio.emit('compare_status', {'diff_percentage': total_percent,795 'compare_msg': msg}, namespace=SOCKETIO_NAMESPACE,796 to=request.sid)797 # Update the message and total percentage in session object798 update_session_diff_transaction(trans_id, session_obj,799 diff_model_obj)800 801 res = view.compare(source_sid=source_sid,802 source_did=source_did,803 target_sid=target_sid,804 target_did=target_did,805 group_name=gettext('Database Objects'),806 ignore_owner=ignore_owner,807 ignore_whitespaces=ignore_whitespaces,808 ignore_tablespace=ignore_tablespace,809 ignore_grants=ignore_grants)810 811 if res is not None:812 comparison_result = comparison_result + res813 total_percent = total_percent + node_percent814 815 return comparison_result, total_percent816 817 818def compare_schema_objects(**kwargs):819 """820 This function is used to compare the specified schema and their children.821 822 :param kwargs:823 :return:824 """825 trans_id = kwargs.get('trans_id')826 session_obj = kwargs.get('session_obj')827 source_sid = kwargs.get('source_sid')828 source_did = kwargs.get('source_did')829 source_scid = kwargs.get('source_scid')830 target_sid = kwargs.get('target_sid')831 target_did = kwargs.get('target_did')832 target_scid = kwargs.get('target_scid')833 schema_name = kwargs.get('schema_name')834 diff_model_obj = kwargs.get('diff_model_obj')835 total_percent = kwargs.get('total_percent')836 node_percent = kwargs.get('node_percent')837 is_schema_source_only = kwargs.get('is_schema_source_only', False)838 ignore_owner = kwargs.get('ignore_owner')839 ignore_whitespaces = kwargs.get('ignore_whitespaces')840 ignore_tablespace = kwargs.get('ignore_tablespace')841 ignore_grants = kwargs.get('ignore_grants')842 843 source_schema_name = None844 if is_schema_source_only:845 driver = get_driver(PG_DEFAULT_DRIVER)846 source_schema_name = driver.qtIdent(None, schema_name)847 848 comparison_result = []849 850 all_registered_nodes = SchemaDiffRegistry.get_registered_nodes()851 for node_name, node_view in all_registered_nodes.items():852 view = SchemaDiffRegistry.get_node_view(node_name)853 if hasattr(view, 'compare'):854 if schema_name == 'Schema Objects':855 msg = gettext('Comparing {0} '). \856 format(gettext(view.blueprint.collection_label))857 else:858 msg = gettext('Comparing {0} of schema \'{1}\''). \859 format(gettext(view.blueprint.collection_label),860 gettext(schema_name))861 app.logger.debug(msg)862 socketio.emit('compare_status', {'diff_percentage': total_percent,863 'compare_msg': msg}, namespace=SOCKETIO_NAMESPACE,864 to=request.sid)865 # Update the message and total percentage in session object866 update_session_diff_transaction(trans_id, session_obj,867 diff_model_obj)868 869 res = view.compare(source_sid=source_sid,870 source_did=source_did,871 source_scid=source_scid,872 target_sid=target_sid,873 target_did=target_did,874 target_scid=target_scid,875 group_name=gettext(schema_name),876 source_schema_name=source_schema_name,877 ignore_owner=ignore_owner,878 ignore_whitespaces=ignore_whitespaces,879 ignore_tablespace=ignore_tablespace,880 ignore_grants=ignore_grants)881 882 if res is not None:883 comparison_result = comparison_result + res884 total_percent = total_percent + node_percent885 # if total_percent is more than 100 then set it to less than 100886 if total_percent >= 100:887 total_percent = 96888 889 return comparison_result, total_percent890 891 892def fetch_compare_schemas(source_sid, source_did, target_sid, target_did):893 """894 This function is used to fetch all the schemas of source and target895 database and compare them.896 897 :param source_sid:898 :param source_did:899 :param target_sid:900 :param target_did:901 :return:902 """903 source_schemas = get_schemas(source_sid, source_did)904 target_schemas = get_schemas(target_sid, target_did)905 906 src_schema_dict = {item['label']: item['_id'] for item in source_schemas}907 tar_schema_dict = {item['label']: item['_id'] for item in target_schemas}908 909 dict1 = copy.deepcopy(src_schema_dict)910 dict2 = copy.deepcopy(tar_schema_dict)911 912 # Find the duplicate keys in both the dictionaries913 dict1_keys = set(dict1.keys())914 dict2_keys = set(dict2.keys())915 intersect_keys = dict1_keys.intersection(dict2_keys)916 917 # Keys that are available in source and missing in target.918 source_only = []919 added = dict1_keys - dict2_keys920 for item in added:921 source_only.append({'schema_name': item,922 'scid': src_schema_dict[item]})923 924 target_only = []925 # Keys that are available in target and missing in source.926 removed = dict2_keys - dict1_keys927 for item in removed:928 target_only.append({'schema_name': item,929 'scid': tar_schema_dict[item]})930 931 in_both_database = []932 for item in intersect_keys:933 in_both_database.append({'schema_name': item,934 'src_scid': src_schema_dict[item],935 'tar_scid': tar_schema_dict[item]})936 937 schema_result = {'source_only': source_only, 'target_only': target_only,938 'in_both_database': in_both_database}939 940 return schema_result941 942 943def compare_pre_validation(trans_id, source_sid, target_sid):944 """945 This function is used to validate transaction id and version compatibility946 :param trans_id:947 :param source_sid:948 :param target_sid:949 :return:950 """951 952 status, error_msg, diff_model_obj, session_obj = \953 check_transaction_status(trans_id)954 955 if error_msg == ERROR_MSG_TRANS_ID_NOT_FOUND:956 res = make_json_response(success=0, errormsg=error_msg, status=404)957 return False, res, None, None958 959 # Server version compatibility check960 status, msg = check_version_compatibility(source_sid, target_sid)961 if not status:962 res = make_json_response(success=0, errormsg=msg, status=428)963 return False, res, None, None964 965 return True, '', diff_model_obj, session_obj966 967 968@socketio.on('connect', namespace=SOCKETIO_NAMESPACE)969def connect():970 """971 Connect to the server through socket.972 :return:973 :rtype:974 """975 socketio.emit('connected', {'sid': request.sid},976 namespace=SOCKETIO_NAMESPACE,977 to=request.sid)978 