pgadmin4/web/pgadmin/tools/schema_diff/__init__.py
2025-01-01 11:26:42 +05:30

977 lines
34 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module implementing the schema_diff frame."""
import json
import pickle
import secrets
import copy
from flask import Response, session, url_for, request
from flask import render_template, current_app as app
from flask_security import current_user
from pgadmin.user_login_check import pga_login_required
from flask_babel import gettext
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response, bad_request, \
make_response as ajax_response, internal_server_error
from pgadmin.model import Server, SharedServer
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.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS,\
ERROR_MSG_TRANS_ID_NOT_FOUND
from sqlalchemy import or_
from pgadmin.authenticate import socket_login_required
from pgadmin import socketio
MODULE_NAME = 'schema_diff'
COMPARE_MSG = gettext("Comparing objects...")
SOCKETIO_NAMESPACE = '/{0}'.format(MODULE_NAME)
SCH_OBJ_STR = 'Schema Objects'
class SchemaDiffModule(PgAdminModule):
"""
class SchemaDiffModule(PgAdminModule)
A module class for Schema Diff derived from PgAdminModule.
"""
LABEL = gettext("Schema Diff")
def get_own_menuitems(self):
return {}
def get_exposed_url_endpoints(self):
"""
Returns:
list: URL endpoints for Schema Diff module
"""
return [
'schema_diff.initialize',
'schema_diff.panel',
'schema_diff.servers',
'schema_diff.databases',
'schema_diff.schemas',
'schema_diff.ddl_compare',
'schema_diff.connect_server',
'schema_diff.connect_database',
'schema_diff.get_server',
'schema_diff.close'
]
def register_preferences(self):
self.preference.register(
'display', 'ignore_whitespaces',
gettext("Ignore Whitespace"), 'boolean', False,
category_label=PREF_LABEL_DISPLAY,
help_str=gettext('Set ignore whitespace on or off by default in '
'the drop-down menu near the Compare button in '
'the Schema Diff tab.')
)
self.preference.register(
'display', 'ignore_owner',
gettext("Ignore Owner"), 'boolean', False,
category_label=PREF_LABEL_DISPLAY,
help_str=gettext('Set ignore owner on or off by default in the '
'drop-down menu near the Compare button in the '
'Schema Diff tab.')
)
self.preference.register(
'display', 'ignore_tablespace',
gettext("Ignore Tablespace"), 'boolean', False,
category_label=PREF_LABEL_DISPLAY,
help_str=gettext('Set ignore tablespace on or off by default in '
'the drop-down menu near the Compare button in '
'the Schema Diff tab.')
)
self.preference.register(
'display', 'ignore_grants',
gettext("Ignore Grants/Revoke"), 'boolean', False,
category_label=PREF_LABEL_DISPLAY,
help_str=gettext('Set ignore grants/revoke on or off by default '
'in the drop-down menu near the Compare button '
'in the Schema Diff tab.')
)
blueprint = SchemaDiffModule(MODULE_NAME, __name__, static_url_path='/static')
@blueprint.route("/")
@pga_login_required
def index():
return bad_request(
errormsg=gettext('This URL cannot be requested directly.')
)
@blueprint.route(
'/panel/<int:trans_id>/<path:editor_title>',
methods=["GET"],
endpoint='panel'
)
def panel(trans_id, editor_title):
"""
This method calls index.html to render the schema diff.
Args:
editor_title: Title of the editor
"""
# If title has slash(es) in it then replace it
if request.args and request.args['fslashes'] != '':
try:
fslashes_list = request.args['fslashes'].split(',')
for idx in fslashes_list:
idx = int(idx)
editor_title = editor_title[:idx] + '/' + editor_title[idx:]
except IndexError as e:
app.logger.exception(e)
return render_template(
"schema_diff/index.html",
_=gettext,
trans_id=trans_id,
editor_title=editor_title,
)
def check_transaction_status(trans_id):
"""
This function is used to check the transaction id
is available in the session object.
Args:
trans_id:
"""
if 'schemaDiff' not in session:
return False, ERROR_MSG_TRANS_ID_NOT_FOUND, None, None
schema_diff_data = session['schemaDiff']
# Return from the function if transaction id not found
if str(trans_id) not in schema_diff_data:
return False, ERROR_MSG_TRANS_ID_NOT_FOUND, None, None
# Fetch the object for the specified transaction id.
# Use pickle.loads function to get the model object
session_obj = schema_diff_data[str(trans_id)]
diff_model_obj = pickle.loads(session_obj['diff_model_obj'])
return True, None, diff_model_obj, session_obj
def update_session_diff_transaction(trans_id, session_obj, diff_model_obj):
"""
This function is used to update the diff model into the session.
:param trans_id:
:param session_obj:
:param diff_model_obj:
:return:
"""
session_obj['diff_model_obj'] = pickle.dumps(diff_model_obj, -1)
if 'schemaDiff' in session:
schema_diff_data = session['schemaDiff']
schema_diff_data[str(trans_id)] = session_obj
session['schemaDiff'] = schema_diff_data
@blueprint.route(
'/initialize/<int:trans_id>',
methods=["GET"],
endpoint="initialize"
)
@pga_login_required
def initialize(trans_id):
"""
This function will initialize the schema diff and return the list
of all the server's.
"""
try:
if 'schemaDiff' not in session:
schema_diff_data = dict()
else:
schema_diff_data = session['schemaDiff']
# Use pickle to store the Schema Diff Model which will be used
# later by the diff module.
schema_diff_data[str(trans_id)] = {
'diff_model_obj': pickle.dumps(SchemaDiffModel(), -1)
}
# Store the schema diff dictionary into the session variable
session['schemaDiff'] = schema_diff_data
except Exception as e:
app.logger.exception(e)
return make_json_response()
@blueprint.route('/close/<int:trans_id>',
methods=["DELETE"],
endpoint='close')
def close(trans_id):
"""
Remove the session details for the particular transaction id.
Args:
trans_id: unique transaction id
"""
if 'schemaDiff' not in session:
return make_json_response(data={'status': True})
schema_diff_data = session['schemaDiff']
# Return from the function if transaction id not found
if str(trans_id) not in schema_diff_data:
return make_json_response(data={'status': True})
try:
# Remove the information of unique transaction id from the
# session variable.
schema_diff_data.pop(str(trans_id), None)
session['schemaDiff'] = schema_diff_data
except Exception as e:
app.logger.error(e)
return internal_server_error(errormsg=str(e))
return make_json_response(data={'status': True})
@blueprint.route(
'/servers',
methods=["GET"],
endpoint="servers"
)
@pga_login_required
def servers():
"""
This function will return the list of servers for the specified
server id.
"""
res = {}
auto_detected_server = None
try:
"""Return a JSON document listing the server groups for the user"""
driver = get_driver(PG_DEFAULT_DRIVER)
from pgadmin.browser.server_groups.servers import\
server_icon_and_background
for server in Server.query.filter(
or_(Server.user_id == current_user.id, Server.shared),
Server.is_adhoc == 0):
shared_server = SharedServer.query.filter_by(
name=server.name, user_id=current_user.id,
servergroup_id=server.servergroup_id).first()
if server.discovery_id:
auto_detected_server = server.name
if shared_server and shared_server.name == auto_detected_server:
continue
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
server_info = {
"value": server.id,
"label": server.name,
"image": server_icon_and_background(connected, manager,
server),
"_id": server.id,
"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)
return make_json_response(data=res)
@blueprint.route(
'/get_server/<int:sid>/<int:did>',
methods=["GET"],
endpoint="get_server"
)
@pga_login_required
def get_server(sid, did):
"""
This function will return the server details for the specified
server id.
"""
res = []
try:
"""Return a JSON document listing the server groups for the user"""
driver = get_driver(PG_DEFAULT_DRIVER)
server = Server.query.filter_by(id=sid).first()
manager = driver.connection_manager(sid)
conn = manager.connection(did=did)
connected = conn.connected()
res = {
"sid": sid,
"name": server.name,
"user": server.username,
"gid": server.servergroup_id,
"type": manager.server_type,
"connected": connected,
"database": conn.db
}
except Exception as e:
app.logger.exception(e)
return make_json_response(data=res)
@blueprint.route(
'/server/connect/<int:sid>',
methods=["POST"],
endpoint="connect_server"
)
@pga_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)
@blueprint.route(
'/database/connect/<int:sid>/<int:did>',
methods=["POST"],
endpoint="connect_database"
)
@pga_login_required
def connect_database(sid, did):
server = Server.query.filter_by(id=sid).first()
view = SchemaDiffRegistry.get_node_view('database')
return view.connect(server.servergroup_id, sid, did)
@blueprint.route(
'/databases/<int:sid>',
methods=["GET"],
endpoint="databases"
)
@pga_login_required
def databases(sid):
"""
This function will return the list of databases for the specified
server id.
"""
res = []
try:
view = SchemaDiffRegistry.get_node_view('database')
server = Server.query.filter_by(id=sid).first()
response = view.nodes(gid=server.servergroup_id, sid=sid,
is_schema_diff=True)
databases = json.loads(response.data)['data']
for db in databases:
res.append({
"value": db['_id'],
"label": db['label'],
"_id": db['_id'],
"connected": db['connected'],
"allowConn": db['allowConn'],
"image": db['icon'],
"canDisconn": db['canDisconn'],
"is_maintenance_db": db['label'] == server.maintenance_db
})
except Exception as e:
app.logger.exception(e)
return make_json_response(data=res)
@blueprint.route(
'/schemas/<int:sid>/<int:did>',
methods=["GET"],
endpoint="schemas"
)
@pga_login_required
def schemas(sid, did):
"""
This function will return the list of schemas for the specified
server id and database id.
"""
res = []
try:
schemas = get_schemas(sid, did)
if schemas is not None:
for sch in schemas:
res.append({
"value": sch['_id'],
"label": sch['label'],
"_id": sch['_id'],
"image": sch['icon'],
})
except Exception as e:
app.logger.exception(e)
return make_json_response(data=res)
@socketio.on('compare_database', namespace=SOCKETIO_NAMESPACE)
@socket_login_required
def compare_database(params):
"""
This function will compare the two databases.
"""
# Check the pre validation before compare
SchemaDiffRegistry.set_schema_diff_compare_mode('Database Objects')
status, error_msg, diff_model_obj, session_obj = \
compare_pre_validation(params['trans_id'], params['source_sid'],
params['target_sid'])
if not status:
socketio.emit('compare_database_failed',
error_msg.json if isinstance(
error_msg, Response) else error_msg,
namespace=SOCKETIO_NAMESPACE, to=request.sid)
return error_msg
comparison_result = []
socketio.emit('compare_status', {'diff_percentage': 0,
'compare_msg': COMPARE_MSG}, namespace=SOCKETIO_NAMESPACE,
to=request.sid)
update_session_diff_transaction(params['trans_id'], session_obj,
diff_model_obj)
try:
ignore_owner = bool(params['ignore_owner'])
ignore_whitespaces = bool(params['ignore_whitespaces'])
ignore_tablespace = bool(params['ignore_tablespace'])
ignore_grants = bool(params['ignore_grants'])
# Fetch all the schemas of source and target database
# Compare them and get the status.
schema_result = \
fetch_compare_schemas(params['source_sid'], params['source_did'],
params['target_sid'], params['target_did'])
total_schema = len(schema_result['source_only']) + len(
schema_result['target_only']) + len(
schema_result['in_both_database'])
node_percent = 0
if total_schema > 0:
node_percent = round(100 / (total_schema * len(
SchemaDiffRegistry.get_registered_nodes())), 2)
total_percent = 0
# Compare Database objects
comparison_schema_result, total_percent = \
compare_database_objects(
trans_id=params['trans_id'], session_obj=session_obj,
source_sid=params['source_sid'],
source_did=params['source_did'],
target_sid=params['target_sid'],
target_did=params['target_did'],
diff_model_obj=diff_model_obj, total_percent=total_percent,
node_percent=node_percent, ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
comparison_result = \
comparison_result + comparison_schema_result
# Compare Schema objects
if 'source_only' in schema_result and \
len(schema_result['source_only']) > 0:
for item in schema_result['source_only']:
comparison_schema_result, total_percent = \
compare_schema_objects(
trans_id=params['trans_id'], session_obj=session_obj,
source_sid=params['source_sid'],
source_did=params['source_did'],
source_scid=item['scid'],
target_sid=params['target_sid'],
target_did=params['target_did'], target_scid=None,
schema_name=item['schema_name'],
diff_model_obj=diff_model_obj,
total_percent=total_percent,
node_percent=node_percent,
is_schema_source_only=True,
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
comparison_result = \
comparison_result + comparison_schema_result
if 'target_only' in schema_result and \
len(schema_result['target_only']) > 0:
for item in schema_result['target_only']:
comparison_schema_result, total_percent = \
compare_schema_objects(
trans_id=params['trans_id'], session_obj=session_obj,
source_sid=params['source_sid'],
source_did=params['source_did'],
source_scid=None, target_sid=params['target_sid'],
target_did=params['target_did'],
target_scid=item['scid'],
schema_name=item['schema_name'],
diff_model_obj=diff_model_obj,
total_percent=total_percent,
node_percent=node_percent,
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
comparison_result = \
comparison_result + comparison_schema_result
# Compare the two schema present in both the databases
if 'in_both_database' in schema_result and \
len(schema_result['in_both_database']) > 0:
for item in schema_result['in_both_database']:
comparison_schema_result, total_percent = \
compare_schema_objects(
trans_id=params['trans_id'], session_obj=session_obj,
source_sid=params['source_sid'],
source_did=params['source_did'],
source_scid=item['src_scid'],
target_sid=params['target_sid'],
target_did=params['target_did'],
target_scid=item['tar_scid'],
schema_name=item['schema_name'],
diff_model_obj=diff_model_obj,
total_percent=total_percent,
node_percent=node_percent,
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
comparison_result = \
comparison_result + comparison_schema_result
# Update the message and total percentage done in session object
update_session_diff_transaction(params['trans_id'], session_obj,
diff_model_obj)
except Exception as e:
app.logger.exception(e)
socketio.emit('compare_database_failed', str(e),
namespace=SOCKETIO_NAMESPACE, to=request.sid)
socketio.emit('compare_database_success', comparison_result,
namespace=SOCKETIO_NAMESPACE, to=request.sid)
@socketio.on('compare_schema', namespace=SOCKETIO_NAMESPACE)
@socket_login_required
def compare_schema(params):
"""
This function will compare the two schema.
"""
# Check the pre validation before compare
SchemaDiffRegistry.set_schema_diff_compare_mode(SCH_OBJ_STR)
status, error_msg, diff_model_obj, session_obj = \
compare_pre_validation(params['trans_id'], params['source_sid'],
params['target_sid'])
if not status:
socketio.emit('compare_schema_failed',
error_msg.json if isinstance(
error_msg, Response) else error_msg,
namespace=SOCKETIO_NAMESPACE, to=request.sid)
return error_msg
comparison_result = []
update_session_diff_transaction(params['trans_id'], session_obj,
diff_model_obj)
try:
ignore_owner = bool(params['ignore_owner'])
ignore_whitespaces = bool(params['ignore_whitespaces'])
ignore_tablespace = bool(params['ignore_tablespace'])
ignore_grants = bool(params['ignore_grants'])
all_registered_nodes = SchemaDiffRegistry.get_registered_nodes()
node_percent = round(100 / len(all_registered_nodes), 2)
total_percent = 0
comparison_schema_result, total_percent = \
compare_schema_objects(
trans_id=params['trans_id'], session_obj=session_obj,
source_sid=params['source_sid'],
source_did=params['source_did'],
source_scid=params['source_scid'],
target_sid=params['target_sid'],
target_did=params['target_did'],
target_scid=params['target_scid'],
schema_name=gettext(SCH_OBJ_STR),
diff_model_obj=diff_model_obj,
total_percent=total_percent,
node_percent=node_percent,
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
comparison_result = \
comparison_result + comparison_schema_result
# Update the message and total percentage done in session object
update_session_diff_transaction(params['trans_id'], session_obj,
diff_model_obj)
except Exception as e:
app.logger.exception(e)
socketio.emit('compare_schema_failed', str(e),
namespace=SOCKETIO_NAMESPACE, to=request.sid)
socketio.emit('compare_schema_success', comparison_result,
namespace=SOCKETIO_NAMESPACE, to=request.sid)
@blueprint.route(
'/ddl_compare/<int:trans_id>/<int:source_sid>/<int:source_did>/'
'<int:source_scid>/<int:target_sid>/<int:target_did>/<int:target_scid>/'
'<int:source_oid>/<int:target_oid>/<node_type>/<comp_status>/',
methods=["GET"],
endpoint="ddl_compare"
)
@pga_login_required
def ddl_compare(trans_id, source_sid, source_did, source_scid,
target_sid, target_did, target_scid, source_oid,
target_oid, node_type, comp_status):
"""
This function is used to compare the specified object and return the
DDL comparison.
"""
# Check the transaction and connection status
_, error_msg, _, _ = \
check_transaction_status(trans_id)
if error_msg == ERROR_MSG_TRANS_ID_NOT_FOUND:
return make_json_response(success=0, errormsg=error_msg, status=404)
view = SchemaDiffRegistry.get_node_view(node_type)
if view and hasattr(view, 'ddl_compare'):
sql = view.ddl_compare(source_sid=source_sid, source_did=source_did,
source_scid=source_scid, target_sid=target_sid,
target_did=target_did, target_scid=target_scid,
source_oid=source_oid, target_oid=target_oid,
comp_status=comp_status)
return ajax_response(
status=200,
response={'source_ddl': sql['source_ddl'],
'target_ddl': sql['target_ddl'],
'diff_ddl': sql['diff_ddl']}
)
msg = gettext('Selected object is not supported for DDL comparison.')
return ajax_response(
status=200,
response={'source_ddl': msg,
'target_ddl': msg,
'diff_ddl': msg
}
)
def check_version_compatibility(sid, tid):
"""Check the version compatibility of source and target servers."""
driver = get_driver(PG_DEFAULT_DRIVER)
src_server = Server.query.filter_by(id=sid).first()
src_manager = driver.connection_manager(src_server.id)
src_conn = src_manager.connection()
tar_server = Server.query.filter_by(id=tid).first()
tar_manager = driver.connection_manager(tar_server.id)
target_conn = tar_manager.connection()
if not (src_conn.connected() and target_conn.connected()):
return False, gettext('Server(s) disconnected.')
if src_manager.server_type != tar_manager.server_type:
return False, gettext('Schema diff does not support the comparison '
'between Postgres Server and EDB Postgres '
'Advanced Server.')
def get_round_val(x):
if x < 100000:
return x + 100 - x % 100
else:
return x + 10000 - x % 10000
if get_round_val(src_manager.version) == \
get_round_val(tar_manager.version):
return True, None
return False, gettext('Source and Target database server must be of '
'the same major version.')
def get_schemas(sid, did):
"""
This function will return the list of schemas for the specified
server id and database id.
"""
try:
view = SchemaDiffRegistry.get_node_view('schema')
server = Server.query.filter_by(id=sid).first()
response = view.nodes(gid=server.servergroup_id, sid=sid, did=did,
is_schema_diff=True)
schemas = json.loads(response.data)['data']
return schemas
except Exception as e:
app.logger.exception(e)
return None
def compare_database_objects(**kwargs):
"""
This function is used to compare the specified schema and their children.
:param kwargs:
:return:
"""
trans_id = kwargs.get('trans_id')
session_obj = kwargs.get('session_obj')
source_sid = kwargs.get('source_sid')
source_did = kwargs.get('source_did')
target_sid = kwargs.get('target_sid')
target_did = kwargs.get('target_did')
diff_model_obj = kwargs.get('diff_model_obj')
total_percent = kwargs.get('total_percent')
node_percent = kwargs.get('node_percent')
ignore_owner = kwargs.get('ignore_owner')
ignore_whitespaces = kwargs.get('ignore_whitespaces')
ignore_tablespace = kwargs.get('ignore_tablespace')
ignore_grants = kwargs.get('ignore_grants')
comparison_result = []
all_registered_nodes = SchemaDiffRegistry.get_registered_nodes(None,
'Database')
for node_name, node_view in all_registered_nodes.items():
view = SchemaDiffRegistry.get_node_view(node_name)
if hasattr(view, 'compare'):
msg = gettext('Comparing {0}'). \
format(gettext(view.blueprint.collection_label))
app.logger.debug(msg)
socketio.emit('compare_status', {'diff_percentage': total_percent,
'compare_msg': msg}, namespace=SOCKETIO_NAMESPACE,
to=request.sid)
# Update the message and total percentage in session object
update_session_diff_transaction(trans_id, session_obj,
diff_model_obj)
res = view.compare(source_sid=source_sid,
source_did=source_did,
target_sid=target_sid,
target_did=target_did,
group_name=gettext('Database Objects'),
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
if res is not None:
comparison_result = comparison_result + res
total_percent = total_percent + node_percent
return comparison_result, total_percent
def compare_schema_objects(**kwargs):
"""
This function is used to compare the specified schema and their children.
:param kwargs:
:return:
"""
trans_id = kwargs.get('trans_id')
session_obj = kwargs.get('session_obj')
source_sid = kwargs.get('source_sid')
source_did = kwargs.get('source_did')
source_scid = kwargs.get('source_scid')
target_sid = kwargs.get('target_sid')
target_did = kwargs.get('target_did')
target_scid = kwargs.get('target_scid')
schema_name = kwargs.get('schema_name')
diff_model_obj = kwargs.get('diff_model_obj')
total_percent = kwargs.get('total_percent')
node_percent = kwargs.get('node_percent')
is_schema_source_only = kwargs.get('is_schema_source_only', False)
ignore_owner = kwargs.get('ignore_owner')
ignore_whitespaces = kwargs.get('ignore_whitespaces')
ignore_tablespace = kwargs.get('ignore_tablespace')
ignore_grants = kwargs.get('ignore_grants')
source_schema_name = None
if is_schema_source_only:
driver = get_driver(PG_DEFAULT_DRIVER)
source_schema_name = driver.qtIdent(None, schema_name)
comparison_result = []
all_registered_nodes = SchemaDiffRegistry.get_registered_nodes()
for node_name, node_view in all_registered_nodes.items():
view = SchemaDiffRegistry.get_node_view(node_name)
if hasattr(view, 'compare'):
if schema_name == SCH_OBJ_STR:
msg = gettext('Comparing {0} '). \
format(gettext(view.blueprint.collection_label))
else:
msg = gettext('Comparing {0} of schema \'{1}\''). \
format(gettext(view.blueprint.collection_label),
gettext(schema_name))
app.logger.debug(msg)
socketio.emit('compare_status', {'diff_percentage': total_percent,
'compare_msg': msg}, namespace=SOCKETIO_NAMESPACE,
to=request.sid)
# Update the message and total percentage in session object
update_session_diff_transaction(trans_id, session_obj,
diff_model_obj)
res = view.compare(source_sid=source_sid,
source_did=source_did,
source_scid=source_scid,
target_sid=target_sid,
target_did=target_did,
target_scid=target_scid,
group_name=gettext(schema_name),
source_schema_name=source_schema_name,
ignore_owner=ignore_owner,
ignore_whitespaces=ignore_whitespaces,
ignore_tablespace=ignore_tablespace,
ignore_grants=ignore_grants)
if res is not None:
comparison_result = comparison_result + res
total_percent = total_percent + node_percent
# if total_percent is more than 100 then set it to less than 100
if total_percent >= 100:
total_percent = 96
return comparison_result, total_percent
def fetch_compare_schemas(source_sid, source_did, target_sid, target_did):
"""
This function is used to fetch all the schemas of source and target
database and compare them.
:param source_sid:
:param source_did:
:param target_sid:
:param target_did:
:return:
"""
source_schemas = get_schemas(source_sid, source_did)
target_schemas = get_schemas(target_sid, target_did)
src_schema_dict = {item['label']: item['_id'] for item in source_schemas}
tar_schema_dict = {item['label']: item['_id'] for item in target_schemas}
dict1 = copy.deepcopy(src_schema_dict)
dict2 = copy.deepcopy(tar_schema_dict)
# Find the duplicate keys in both the dictionaries
dict1_keys = set(dict1.keys())
dict2_keys = set(dict2.keys())
intersect_keys = dict1_keys.intersection(dict2_keys)
# Keys that are available in source and missing in target.
source_only = []
added = dict1_keys - dict2_keys
for item in added:
source_only.append({'schema_name': item,
'scid': src_schema_dict[item]})
target_only = []
# Keys that are available in target and missing in source.
removed = dict2_keys - dict1_keys
for item in removed:
target_only.append({'schema_name': item,
'scid': tar_schema_dict[item]})
in_both_database = []
for item in intersect_keys:
in_both_database.append({'schema_name': item,
'src_scid': src_schema_dict[item],
'tar_scid': tar_schema_dict[item]})
schema_result = {'source_only': source_only, 'target_only': target_only,
'in_both_database': in_both_database}
return schema_result
def compare_pre_validation(trans_id, source_sid, target_sid):
"""
This function is used to validate transaction id and version compatibility
:param trans_id:
:param source_sid:
:param target_sid:
:return:
"""
status, error_msg, diff_model_obj, session_obj = \
check_transaction_status(trans_id)
if error_msg == ERROR_MSG_TRANS_ID_NOT_FOUND:
res = make_json_response(success=0, errormsg=error_msg, status=404)
return False, res, None, None
# Server version compatibility check
status, msg = check_version_compatibility(source_sid, target_sid)
if not status:
res = make_json_response(success=0, errormsg=msg, status=428)
return False, res, None, None
return True, '', diff_model_obj, session_obj
@socketio.on('connect', namespace=SOCKETIO_NAMESPACE)
def connect():
"""
Connect to the server through socket.
:return:
:rtype:
"""
socketio.emit('connected', {'sid': request.sid},
namespace=SOCKETIO_NAMESPACE,
to=request.sid)