pgadmin4/web/pgadmin/tools/schema_diff/__init__.py
Aditya Toshniwal 862f101772
Significant changes to use ReactJS extensively.
1. Replace the current layout library wcDocker with ReactJS based rc-dock. #6479
2. Have close buttons on individual panel tabs instead of common. #2821
3. Changes in the context menu on panel tabs - Add close, close all and close others menu items. #5394
4. Allow closing all the tabs, including SQL and Properties. #4733
5. Changes in docking behaviour of different tabs based on user requests and remove lock layout menu.
6. Fix an issue where the scroll position of panels was not remembered on Firefox. #2986
7. Reset layout now will not require page refresh and is done spontaneously.
8. Use the zustand store for storing preferences instead of plain JS objects. This will help reflecting preferences immediately.
9. The above fix incorrect format (no indent) of SQL stored functions/procedures. #6720
10. New version check is moved to an async request now instead of app start to improve startup performance.
11. Remove jQuery and Bootstrap completely.
12. Replace jasmine and karma test runner with jest. Migrate all the JS test cases to jest. This will save time in writing and debugging JS tests.
13. Other important code improvements and cleanup.
2023-10-23 17:43:17 +05:30

977 lines
34 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2023, 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, 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)
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("/")
@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',
methods=["GET"],
endpoint="initialize"
)
@login_required
def initialize():
"""
This function will initialize the schema diff and return the list
of all the server's.
"""
trans_id = None
try:
# Create a unique id for the transaction
trans_id = str(secrets.choice(range(1, 9999999)))
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[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(
data={'schemaDiffTransId': trans_id})
@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"
)
@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)):
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"
)
@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"
)
@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"
)
@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"
)
@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"
)
@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
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
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('Schema Objects'),
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"
)
@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
status, error_msg, diff_model_obj, session_obj = \
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 == 'Schema Objects':
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)