mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-09 23:15:58 -06:00
Added the ability to generate ERDs for tables. #4756
This commit is contained in:
parent
beb43c69dc
commit
af32e3c296
Binary file not shown.
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 117 KiB |
@ -196,6 +196,10 @@ Use the fields on the *Options* panel to manage ERD preferences.
|
||||
generated by the ERD Tool will add DROP table DDL before each CREATE
|
||||
table DDL.
|
||||
|
||||
* *Table Relation Depth* is useful when generating an ERD for a table.
|
||||
It allows to set the limit on the depth level pgAdmin should traverse
|
||||
to find the relations. Use -1 to set no limit.
|
||||
|
||||
The Graphs Node
|
||||
***************
|
||||
|
||||
|
@ -121,7 +121,11 @@ define('pgadmin.node.table', [
|
||||
applies: ['object', 'context'], callback: 'count_table_rows',
|
||||
category: 'Count', priority: 2, label: gettext('Count Rows'),
|
||||
enable: true,
|
||||
},
|
||||
},{
|
||||
name: 'generate_erd', node: 'table', module: this,
|
||||
applies: ['object', 'context'], callback: 'generate_erd',
|
||||
category: 'erd', priority: 5, label: gettext('ERD For Table'),
|
||||
}
|
||||
]);
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin:browser:node:table:updated', this.onTableUpdated, this
|
||||
@ -289,6 +293,14 @@ define('pgadmin.node.table', [
|
||||
t.unload(i);
|
||||
});
|
||||
},
|
||||
/* Generate the ERD */
|
||||
generate_erd: function(args) {
|
||||
let input = args || {},
|
||||
t = pgBrowser.tree,
|
||||
i = input.item || t.selected(),
|
||||
d = i ? t.itemData(i) : undefined;
|
||||
pgAdmin.Tools.ERD.showErdTool(d, i, true);
|
||||
},
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser);
|
||||
|
@ -13,6 +13,7 @@ SELECT ct.oid,
|
||||
confrelid,
|
||||
nl.nspname as fknsp,
|
||||
cl.relname as fktab,
|
||||
nr.oid as refnspoid,
|
||||
nr.nspname as refnsp,
|
||||
cr.relname as reftab,
|
||||
description as comment,
|
||||
|
@ -0,0 +1,10 @@
|
||||
SELECT
|
||||
co.conrelid as confrelid, co.conname, nl.oid as refnspoid
|
||||
FROM pg_catalog.pg_depend dep
|
||||
JOIN pg_catalog.pg_constraint co ON dep.objid=co.oid
|
||||
JOIN pg_catalog.pg_class cl ON cl.oid=co.conrelid
|
||||
JOIN pg_catalog.pg_namespace nl ON nl.oid=cl.relnamespace
|
||||
WHERE dep.refobjid={{oid}}::OID
|
||||
AND deptype = 'n'
|
||||
|
||||
|
@ -395,6 +395,22 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
|
||||
status=200
|
||||
)
|
||||
|
||||
def get_fk_ref_tables(self, tid):
|
||||
"""
|
||||
This function get the depending tables of the current table.
|
||||
The tables depending on tid table using FK relation.
|
||||
Args:
|
||||
tid: Table ID
|
||||
"""
|
||||
sql = render_template("/".join([self.table_template_path,
|
||||
'fk_ref_tables.sql']), oid=tid)
|
||||
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
if not status:
|
||||
return status, res
|
||||
|
||||
return status, res['rows']
|
||||
|
||||
def get_table_statistics(self, scid, tid):
|
||||
"""
|
||||
Statistics
|
||||
|
@ -98,7 +98,7 @@ define('pgadmin.node.database', [
|
||||
},{
|
||||
name: 'generate_erd', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'generate_erd',
|
||||
category: 'erd', priority: 5, label: gettext('Generate ERD'),
|
||||
category: 'erd', priority: 5, label: gettext('ERD For Database'),
|
||||
enable: (node) => {
|
||||
return node.allowConn;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import { io } from 'socketio';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
export function openSocket(namespace, options) {
|
||||
return new Promise((resolve, reject)=>{
|
||||
@ -41,5 +42,8 @@ export function socketApiGet(socket, endpoint, params) {
|
||||
socket.on(`${endpoint}_failed`, (data)=>{
|
||||
reject(data);
|
||||
});
|
||||
socket.on('disconnect', ()=>{
|
||||
reject(gettext('Connection to pgAdmin server has been lost'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -386,6 +386,20 @@ class ERDModule(PgAdminModule):
|
||||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'options',
|
||||
'table_relation_depth',
|
||||
gettext('Table Relation Depth'),
|
||||
'integer',
|
||||
-1,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'The maximum depth pgAdmin should traverse to find '
|
||||
'related tables when generating an ERD for a table. '
|
||||
'Use -1 for no limit.'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
blueprint = ERDModule(MODULE_NAME, __name__, static_url_path='/static')
|
||||
|
||||
@ -621,7 +635,9 @@ def tables(params):
|
||||
try:
|
||||
helper = ERDHelper(params['trans_id'], params['sid'], params['did'])
|
||||
_get_connection(params['sid'], params['did'], params['trans_id'])
|
||||
status, tables = helper.get_all_tables()
|
||||
|
||||
status, tables = helper.get_all_tables(params.get('scid', None),
|
||||
params.get('tid', None))
|
||||
|
||||
if not status:
|
||||
socketio.emit('tables_failed', tables,
|
||||
|
@ -193,6 +193,11 @@ export default class ERDModule {
|
||||
+`&did=${parentData.database._id}`
|
||||
+`&gen=${gen}`;
|
||||
|
||||
if(parentData.table) {
|
||||
openUrl += `&scid=${parentData.schema._id}`
|
||||
+`&tid=${parentData.table._id}`;
|
||||
}
|
||||
|
||||
return openUrl;
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,6 @@ class ERDTool extends React.Component {
|
||||
} else {
|
||||
window.removeEventListener('beforeunload', this.onBeforeUnload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -660,7 +659,7 @@ class ERDTool extends React.Component {
|
||||
}
|
||||
|
||||
onSQLClick(sqlWithDrop=false) {
|
||||
let scriptHeader = gettext('-- This script was generated by a beta version of the ERD tool in pgAdmin 4.\n');
|
||||
let scriptHeader = gettext('-- This script was generated by the ERD tool in pgAdmin 4.\n');
|
||||
scriptHeader += gettext('-- Please log an issue at https://redmine.postgresql.org/projects/pgadmin4/issues/new if you find any bugs, including reproduction steps.\n');
|
||||
|
||||
let url = url_for('erd.sql', {
|
||||
@ -926,12 +925,18 @@ class ERDTool extends React.Component {
|
||||
sgid: parseInt(this.props.params.sgid),
|
||||
sid: parseInt(this.props.params.sid),
|
||||
did: parseInt(this.props.params.did),
|
||||
scid: this.props.params.scid ? parseInt(this.props.params.scid) : undefined,
|
||||
tid: this.props.params.tid ? parseInt(this.props.params.tid) : undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
this.handleAxiosCatch(error);
|
||||
}
|
||||
socket?.disconnect();
|
||||
this.diagram.deserializeData(resData);
|
||||
try {
|
||||
this.diagram.deserializeData(resData);
|
||||
} catch (error) {
|
||||
this.handleAxiosCatch(error);
|
||||
}
|
||||
this.setLoading(null);
|
||||
}
|
||||
|
||||
@ -964,6 +969,8 @@ ERDTool.propTypes = {
|
||||
sgid: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
sid: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
did: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
scid: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
tid: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
server_type: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
bgcolor: PropTypes.string,
|
||||
|
@ -13,6 +13,7 @@ from pgadmin.browser.server_groups.servers.databases.schemas.utils \
|
||||
import get_schemas
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.utils \
|
||||
import DataTypeReader
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
|
||||
|
||||
class ERDTableView(BaseTableView, DataTypeReader):
|
||||
@ -31,7 +32,7 @@ class ERDTableView(BaseTableView, DataTypeReader):
|
||||
return DataTypeReader.get_types(self, self.conn, condition, True)
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def fetch_all_tables(self, conn_id=None, did=None, sid=None):
|
||||
def fetch_all_tables(self, did=None, sid=None):
|
||||
status, schemas = get_schemas(self.conn, show_system_objects=False)
|
||||
if not status:
|
||||
return status, schemas
|
||||
@ -47,6 +48,44 @@ class ERDTableView(BaseTableView, DataTypeReader):
|
||||
|
||||
return True, all_tables
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def traverse_related_tables(self, did=None, sid=None, scid=None,
|
||||
tid=None, related={}, maxdepth=0, currdepth=0):
|
||||
|
||||
status, res = \
|
||||
BaseTableView.fetch_tables(self, sid, did, scid, tid=tid)
|
||||
|
||||
if not status:
|
||||
return status, res
|
||||
|
||||
related[tid] = res
|
||||
# Max depth limit reached
|
||||
if currdepth == maxdepth:
|
||||
new_fks = []
|
||||
for fk in related[tid].pop('foreign_key', []):
|
||||
if fk['confrelid'] in related:
|
||||
new_fks.append(fk)
|
||||
|
||||
related[tid]['foreign_key'] = new_fks
|
||||
return True, None
|
||||
|
||||
status, depending_res = BaseTableView.get_fk_ref_tables(
|
||||
self, tid)
|
||||
|
||||
if not status:
|
||||
return status, depending_res
|
||||
|
||||
for fk in [*res.get('foreign_key', []), *depending_res]:
|
||||
if fk['confrelid'] in related:
|
||||
continue
|
||||
status, res = self.traverse_related_tables(
|
||||
did=did, sid=sid, scid=fk['refnspoid'], tid=fk['confrelid'],
|
||||
related=related, maxdepth=maxdepth, currdepth=currdepth + 1)
|
||||
if not status:
|
||||
return status, res
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
class ERDHelper:
|
||||
def __init__(self, conn_id, sid, did):
|
||||
@ -66,8 +105,18 @@ class ERDHelper:
|
||||
data=data, with_drop=with_drop)
|
||||
return SQL
|
||||
|
||||
def get_all_tables(self):
|
||||
status, res = self.table_view.fetch_all_tables(
|
||||
conn_id=self.conn_id, did=self.did, sid=self.sid)
|
||||
|
||||
def get_all_tables(self, scid, tid):
|
||||
if tid is None and scid is None:
|
||||
status, res = self.table_view.fetch_all_tables(
|
||||
did=self.did, sid=self.sid)
|
||||
else:
|
||||
prefs = Preferences.module('erd')
|
||||
table_relation_depth = prefs.preference('table_relation_depth')
|
||||
related = {}
|
||||
status, res = self.table_view.traverse_related_tables(
|
||||
did=self.did, sid=self.sid, scid=scid, tid=tid,
|
||||
related=related, maxdepth=table_relation_depth.get()
|
||||
)
|
||||
if status:
|
||||
res = list(related.values())
|
||||
return status, res
|
||||
|
@ -393,7 +393,7 @@ describe('ERDTool', ()=>{
|
||||
bodyInstance.onSQLClick();
|
||||
|
||||
setTimeout(()=>{
|
||||
let sql = '-- This script was generated by a beta version of the ERD tool in pgAdmin 4.\n'
|
||||
let sql = '-- This script was generated by the ERD tool in pgAdmin 4.\n'
|
||||
+ '-- Please log an issue at https://redmine.postgresql.org/projects/pgadmin4/issues/new if you find any bugs, including reproduction steps.\n'
|
||||
+ 'BEGIN;\nSELECT 1;\nEND;';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user