mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support of Foreign Tables to the Schema Diff. Fixes #5263
This commit is contained in:
@@ -223,6 +223,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
'compare': [{'get': 'compare'}, {'get': 'compare'}]
|
||||
})
|
||||
|
||||
keys_to_ignore = ['oid', 'basensp', 'oid-2', 'attnum', 'strftoptions',
|
||||
'relacl']
|
||||
|
||||
def validate_request(f):
|
||||
"""
|
||||
Works as a decorator.
|
||||
@@ -697,7 +700,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
@check_precondition
|
||||
def delete(self, gid, sid, did, scid, foid=None):
|
||||
def delete(self, gid, sid, did, scid, foid=None, only_sql=False):
|
||||
"""
|
||||
Drops the Foreign Table.
|
||||
|
||||
@@ -707,6 +710,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
did: Database Id
|
||||
scid: Schema Id
|
||||
foid: Foreign Table Id
|
||||
only_sql: Return only sql if True
|
||||
"""
|
||||
if foid is None:
|
||||
data = request.form if request.form else json.loads(
|
||||
@@ -750,6 +754,11 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
name=name,
|
||||
basensp=basensp,
|
||||
cascade=cascade)
|
||||
|
||||
# Used for schema diff tool
|
||||
if only_sql:
|
||||
return SQL
|
||||
|
||||
status, res = self.conn.execute_scalar(SQL)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
@@ -808,7 +817,8 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
@check_precondition
|
||||
def sql(self, gid, sid, did, scid, foid=None):
|
||||
def sql(self, gid, sid, did, scid, foid=None, diff_schema=None,
|
||||
json_resp=True):
|
||||
"""
|
||||
Returns the SQL for the Foreign Table object.
|
||||
|
||||
@@ -818,12 +828,17 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
did: Database Id
|
||||
scid: Schema Id
|
||||
foid: Foreign Table Id
|
||||
diff_schema: Target Schema for schema diff
|
||||
json_resp: True then return json response
|
||||
"""
|
||||
status, data = self._fetch_properties(gid, sid, did, scid, foid,
|
||||
inherits=True)
|
||||
if not status:
|
||||
return data
|
||||
|
||||
if diff_schema:
|
||||
data['basensp'] = diff_schema
|
||||
|
||||
col_data = []
|
||||
for c in data['columns']:
|
||||
if ('inheritedfrom' not in c) or (c['inheritedfrom'] is None):
|
||||
@@ -839,6 +854,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
SQL = render_template("/".join([self.template_path,
|
||||
'create.sql']), data=data, is_sql=True)
|
||||
|
||||
if not json_resp:
|
||||
return SQL.strip('\n')
|
||||
|
||||
sql_header = u"""-- FOREIGN TABLE: {0}
|
||||
|
||||
-- DROP FOREIGN TABLE {0};
|
||||
@@ -883,7 +901,8 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
except Exception as e:
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
def get_sql(self, gid, sid, did, scid, data, foid=None):
|
||||
def get_sql(self, gid, sid, did, scid, data, foid=None,
|
||||
is_schema_diff=False):
|
||||
"""
|
||||
Genrates the SQL statements to create/update the Foreign Table.
|
||||
|
||||
@@ -893,6 +912,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
did: Database Id
|
||||
scid: Schema Id
|
||||
foid: Foreign Table Id
|
||||
is_schema_diff: True is function gets called from schema diff
|
||||
"""
|
||||
if foid is not None:
|
||||
status, old_data = self._fetch_properties(gid, sid, did, scid,
|
||||
@@ -900,6 +920,10 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
if not status:
|
||||
return old_data
|
||||
|
||||
if is_schema_diff:
|
||||
data['is_schema_diff'] = True
|
||||
old_data['columns_for_schema_diff'] = old_data['columns']
|
||||
|
||||
# Prepare dict of columns with key = column's attnum
|
||||
# Will use this in the update template when any column is
|
||||
# changed, to identify the columns.
|
||||
@@ -960,10 +984,21 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
data['acl']['deleted'] = parse_priv_to_db(
|
||||
data['acl']['deleted'], ["a", "r", "w", "x"])
|
||||
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
data=data, o_data=old_data
|
||||
)
|
||||
# If ftsrvname is changed while comparing two schemas
|
||||
# then we need to drop foreign table and recreate it
|
||||
if is_schema_diff and 'ftsrvname' in data:
|
||||
# Modify the data required to recreate the foreign table.
|
||||
self.modify_data_for_schema_diff(data, old_data)
|
||||
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path,
|
||||
'foreign_table_schema_diff.sql']),
|
||||
data=data, o_data=old_data)
|
||||
else:
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
data=data, o_data=old_data
|
||||
)
|
||||
return SQL, data['name'] if 'name' in data else old_data['name']
|
||||
else:
|
||||
data['columns'] = self._format_columns(data['columns'])
|
||||
@@ -1328,7 +1363,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
return ajax_response(response=sql)
|
||||
|
||||
@check_precondition
|
||||
def delete_sql(self, gid, sid, did, scid, foid):
|
||||
def delete_sql(self, gid, sid, did, scid, foid, only_sql=False):
|
||||
"""
|
||||
DELETE script sql for the object
|
||||
|
||||
@@ -1338,6 +1373,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
did: Database Id
|
||||
scid: Schema Id
|
||||
foid: Foreign Table Id
|
||||
only_sql: Return only sql if True
|
||||
|
||||
Returns:
|
||||
DELETE Script sql for the object
|
||||
@@ -1350,6 +1386,10 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
self.qtIdent(self.conn, data['basensp'], data['name'])
|
||||
)
|
||||
|
||||
# Used for schema diff tool
|
||||
if only_sql:
|
||||
return sql
|
||||
|
||||
return ajax_response(response=sql)
|
||||
|
||||
@check_precondition
|
||||
@@ -1384,5 +1424,86 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
|
||||
|
||||
return res
|
||||
|
||||
def get_sql_from_diff(self, gid, sid, did, scid, oid, data=None,
|
||||
diff_schema=None, drop_sql=False):
|
||||
"""
|
||||
This function is used to get the DDL/DML statements.
|
||||
:param gid: Group ID
|
||||
:param sid: Serve ID
|
||||
:param did: Database ID
|
||||
:param scid: Schema ID
|
||||
:param oid: Collation ID
|
||||
:param data: Difference data
|
||||
:param diff_schema: Target Schema
|
||||
:param drop_sql: True if need to drop the domains
|
||||
:return:
|
||||
"""
|
||||
sql = ''
|
||||
if data:
|
||||
if diff_schema:
|
||||
data['schema'] = diff_schema
|
||||
sql, name = self.get_sql(gid=gid, sid=sid, did=did, scid=scid,
|
||||
data=data, foid=oid,
|
||||
is_schema_diff=True)
|
||||
else:
|
||||
if drop_sql:
|
||||
sql = self.delete(gid=gid, sid=sid, did=did,
|
||||
scid=scid, foid=oid, only_sql=True)
|
||||
elif diff_schema:
|
||||
sql = self.sql(gid=gid, sid=sid, did=did, scid=scid, foid=oid,
|
||||
diff_schema=diff_schema, json_resp=False)
|
||||
else:
|
||||
sql = self.sql(gid=gid, sid=sid, did=did, scid=scid, foid=oid,
|
||||
json_resp=False)
|
||||
return sql
|
||||
|
||||
def modify_data_for_schema_diff(self, data, old_data):
|
||||
"""
|
||||
This function modifies the data for columns, constraints, options
|
||||
etc...
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
tmp_columns = []
|
||||
if 'columns_for_schema_diff' in old_data:
|
||||
tmp_columns = old_data['columns_for_schema_diff']
|
||||
if 'columns' in data:
|
||||
if 'added' in data['columns']:
|
||||
for item in data['columns']['added']:
|
||||
tmp_columns.append(item)
|
||||
if 'changed' in data['columns']:
|
||||
for item in data['columns']['changed']:
|
||||
tmp_columns.append(item)
|
||||
if 'deleted' in data['columns']:
|
||||
for item in data['columns']['deleted']:
|
||||
tmp_columns.remove(item)
|
||||
data['columns'] = tmp_columns
|
||||
|
||||
tmp_constraints = []
|
||||
if 'constraints' in data:
|
||||
if 'added' in data['constraints']:
|
||||
for item in data['constraints']['added']:
|
||||
tmp_constraints.append(item)
|
||||
if 'changed' in data['constraints']:
|
||||
for item in data['constraints']['changed']:
|
||||
tmp_constraints.append(item)
|
||||
data['constraints'] = tmp_constraints
|
||||
|
||||
tmp_ftoptions = []
|
||||
if 'ftoptions' in old_data:
|
||||
tmp_ftoptions = old_data['ftoptions']
|
||||
if 'ftoptions' in data:
|
||||
if 'added' in data['ftoptions']:
|
||||
for item in data['ftoptions']['added']:
|
||||
tmp_ftoptions.append(item)
|
||||
if 'changed' in data['ftoptions']:
|
||||
for item in data['ftoptions']['changed']:
|
||||
tmp_ftoptions.append(item)
|
||||
if 'deleted' in data['ftoptions']:
|
||||
for item in data['ftoptions']['deleted']:
|
||||
tmp_ftoptions.remove(item)
|
||||
data['ftoptions'] = tmp_ftoptions
|
||||
|
||||
|
||||
SchemaDiffRegistry(blueprint.node_type, ForeignTableView)
|
||||
ForeignTableView.register_node_view(blueprint)
|
||||
|
||||
@@ -16,7 +16,7 @@ LEFT OUTER JOIN
|
||||
LEFT OUTER JOIN
|
||||
pg_type b ON t.typelem=b.oid
|
||||
LEFT OUTER JOIN
|
||||
pg_collation cl ON t.typcollation=cl.oid
|
||||
pg_collation cl ON att.attcollation=cl.oid
|
||||
LEFT OUTER JOIN
|
||||
pg_namespace cn ON cl.collnamespace=cn.oid
|
||||
WHERE
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{% import 'macros/schemas/security.macros' as SECLABEL %}
|
||||
{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
|
||||
{% set is_columns = [] %}
|
||||
{% if data.columns %}{% set columns = data.columns %}{% elif o_data.columns %}{% set columns = o_data.columns %}{% endif %}
|
||||
{% if data.inherits %}{% set inherits = data.inherits %}{% elif o_data.columns %}{% set inherits = o_data.inherits %}{% endif %}
|
||||
{% if data.ftoptions %}{% set ftoptions = data.ftoptions %}{% elif o_data.ftoptions %}{% set ftoptions = o_data.ftoptions %}{% endif %}
|
||||
{% if data.constraints %}{% set constraints = data.constraints %}{% elif o_data.constraints %}{% set constraints = o_data.constraints %}{% endif %}
|
||||
{% if data.acl %}{% set acl = data.acl %}{% elif o_data.acl %}{% set acl = o_data.acl %}{% endif %}
|
||||
-- WARNING:
|
||||
-- We have found the difference in foreign server
|
||||
-- so we need to drop the existing foreign table first and re-create it.
|
||||
DROP FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }};
|
||||
|
||||
CREATE FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}(
|
||||
{% if columns %}
|
||||
{% for c in columns %}
|
||||
{% if (not c.inheritedfrom or c.inheritedfrom =='' or c.inheritedfrom == None or c.inheritedfrom == 'None' ) %}
|
||||
{% if is_columns.append('1') %}{% endif %}
|
||||
{{conn|qtIdent(c.attname)}} {% if is_sql %}{{ c.fulltype }}{% else %}{{c.datatype }}{% if c.typlen %}({{c.typlen}}{% if c.precision %}, {{c.precision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}{% endif %}{% if c.coloptions %}
|
||||
{% for o in c.coloptions %}{% if o.option and o.value %}
|
||||
{% if loop.first %} OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral}}{% if loop.last %}){% endif %}{% endif %}
|
||||
{% endfor %}{% endif %}
|
||||
{% if c.attnotnull %} NOT NULL{% else %} NULL{% endif %}
|
||||
{% if c.typdefault is defined and c.typdefault is not none %} DEFAULT {{c.typdefault}}{% endif %}
|
||||
{% if c.collname %} COLLATE {{c.collname}}{% endif %}
|
||||
{% if not loop.last %},
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
)
|
||||
{% if inherits %}
|
||||
INHERITS ({% for i in inherits %}{% if i %}{{i}}{% if not loop.last %}, {% endif %}{% endif %}{% endfor %})
|
||||
{% endif %}
|
||||
SERVER {{ conn|qtIdent(data.ftsrvname) }}{% if ftoptions %}
|
||||
|
||||
{% for o in ftoptions %}
|
||||
{% if o.option and o.value %}
|
||||
{% if loop.first %} OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral}}{% if loop.last %}){% endif %}{% endif %}
|
||||
{% endfor %}{% endif %};
|
||||
{% if data.owner %}
|
||||
|
||||
ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}
|
||||
OWNER TO {{ data.owner }};
|
||||
{% endif -%}
|
||||
{% if constraints %}
|
||||
{% for c in constraints %}
|
||||
|
||||
ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}
|
||||
ADD CONSTRAINT {{ conn|qtIdent(c.conname) }} CHECK ({{ c.consrc }}){% if not c.convalidated %} NOT VALID{% endif %}{% if c.connoinherit %} NO INHERIT{% endif %};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if data.description %}
|
||||
|
||||
COMMENT ON FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}
|
||||
IS '{{ data.description }}';
|
||||
{ % elif o_data.description %}
|
||||
|
||||
COMMENT ON FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}
|
||||
IS '{{ o_data.description }}';
|
||||
{% endif -%}
|
||||
{% if acl %}
|
||||
|
||||
{% for priv in acl %}
|
||||
{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, o_data.name, priv.without_grant, priv.with_grant, o_data.basensp) }}
|
||||
{% endfor -%}
|
||||
{% endif -%}
|
||||
{% if data.seclabels %}
|
||||
{% for r in data.seclabels %}
|
||||
{% if r.label and r.provider %}
|
||||
|
||||
{{ SECLABEL.SET(conn, 'FOREIGN TABLE', o_data.name, r.provider, r.label, o_data.basensp) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -34,7 +34,7 @@ LEFT OUTER JOIN
|
||||
LEFT OUTER JOIN
|
||||
pg_type b ON t.typelem=b.oid
|
||||
LEFT OUTER JOIN
|
||||
pg_collation cl ON t.typcollation=cl.oid
|
||||
pg_collation cl ON att.attcollation=cl.oid
|
||||
LEFT OUTER JOIN
|
||||
pg_namespace cn ON cl.collnamespace=cn.oid
|
||||
WHERE
|
||||
|
||||
@@ -121,6 +121,16 @@ ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, name) }}
|
||||
ADD CONSTRAINT {{ conn|qtIdent(c.conname) }} CHECK ({{ c.consrc }}){% if not c.convalidated %} NOT VALID{% endif %}{% if c.connoinherit %} NO INHERIT{% endif %};
|
||||
|
||||
{% endfor %}
|
||||
{% if data.is_schema_diff is defined and data.is_schema_diff %}
|
||||
{% for c in data.constraints.changed %}
|
||||
ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, name) }}
|
||||
DROP CONSTRAINT {{conn|qtIdent(c.conname)}};
|
||||
|
||||
ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, name) }}
|
||||
ADD CONSTRAINT {{ conn|qtIdent(c.conname) }} CHECK ({{ c.consrc }}){% if not c.convalidated %} NOT VALID{% endif %}{% if c.connoinherit %} NO INHERIT{% endif %};
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for c in data.constraints.changed %}
|
||||
{% if c.convalidated %}
|
||||
ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, name) }}
|
||||
@@ -129,6 +139,7 @@ ALTER FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, name) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if data.ftoptions %}
|
||||
{% for o in data.ftoptions.deleted %}
|
||||
{% if o.option and o.value %}
|
||||
|
||||
@@ -30,7 +30,7 @@ from pgadmin.utils.compile_template_name import compile_template_path
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
|
||||
from pgadmin.tools.schema_diff.directory_compare import directory_diff,\
|
||||
parce_acl
|
||||
parse_acl
|
||||
|
||||
|
||||
# If we are in Python3
|
||||
@@ -1012,7 +1012,7 @@ class CompoundTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
source, target,
|
||||
ignore_keys=self.keys_to_ignore, difference={}
|
||||
)
|
||||
diff_dict.update(parce_acl(source, target))
|
||||
parse_acl(source, target, diff_dict)
|
||||
|
||||
diff = self.get_sql_from_diff(gid=tgt_params['gid'],
|
||||
sid=tgt_params['sid'],
|
||||
|
||||
@@ -29,7 +29,7 @@ from pgadmin.utils import IS_PY2
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
|
||||
from pgadmin.tools.schema_diff.directory_compare import directory_diff,\
|
||||
parce_acl
|
||||
parse_acl
|
||||
|
||||
# If we are in Python3
|
||||
if not IS_PY2:
|
||||
@@ -680,7 +680,7 @@ class RuleView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
source, target,
|
||||
ignore_keys=self.keys_to_ignore, difference={}
|
||||
)
|
||||
diff_dict.update(parce_acl(source, target))
|
||||
parse_acl(source, target, diff_dict)
|
||||
|
||||
diff = self.get_sql_from_diff(gid=tgt_params['gid'],
|
||||
sid=tgt_params['sid'],
|
||||
|
||||
@@ -30,7 +30,7 @@ from pgadmin.utils import IS_PY2
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
|
||||
from pgadmin.tools.schema_diff.directory_compare import directory_diff,\
|
||||
parce_acl
|
||||
parse_acl
|
||||
|
||||
# If we are in Python3
|
||||
if not IS_PY2:
|
||||
@@ -1007,7 +1007,7 @@ class TriggerView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
source, target,
|
||||
ignore_keys=self.keys_to_ignore, difference={}
|
||||
)
|
||||
diff_dict.update(parce_acl(source, target))
|
||||
parse_acl(source, target, diff_dict)
|
||||
|
||||
diff = self.get_sql_from_diff(gid=tgt_params['gid'],
|
||||
sid=tgt_params['sid'],
|
||||
|
||||
Reference in New Issue
Block a user