1) Added support for advanced table fields like the foreign key, primary key in the ERD tool. Fixes #6081

2) Added index creation when generating SQL in the ERD tool. Fixes #6529
This commit is contained in:
Aditya Toshniwal
2021-10-11 17:42:14 +05:30
committed by Akshay Joshi
parent 9796f50362
commit a92c1b43a2
26 changed files with 900 additions and 1416 deletions

View File

@@ -19,7 +19,7 @@ export function getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser) {
}
export default class ColumnSchema extends BaseUISchema {
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions) {
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions, inErd=false) {
super({
name: undefined,
attowner: undefined,
@@ -57,6 +57,7 @@ export default class ColumnSchema extends BaseUISchema {
this.nodeInfo = nodeInfo;
this.cltypeOptions = cltypeOptions;
this.collspcnameOptions = collspcnameOptions;
this.inErd = inErd;
this.datatypes = [];
}
@@ -71,8 +72,7 @@ export default class ColumnSchema extends BaseUISchema {
return true;
}
if('schema' in this.nodeInfo)
{
if(this.nodeInfo && ('schema' in this.nodeInfo)) {
// We will disable control if it's system columns
// inheritedfrom check is useful when we use this schema in table node
// inheritedfrom has value then we should disable it
@@ -86,7 +86,7 @@ export default class ColumnSchema extends BaseUISchema {
// ie: it's position is less than 1
return !(!_.isUndefined(state.attnum) && state.attnum > 0);
}
return true;
return false;
}
editableCheckForTable(state) {
@@ -179,7 +179,7 @@ export default class ColumnSchema extends BaseUISchema {
) || (
'is_partitioned' in obj.top.origData
&& obj.top.origData['is_partitioned']
&& obj.nodeInfo.server && obj.nodeInfo.server.version < 11000
&& obj.getServerVersion() < 11000
))
) {
return true;
@@ -239,7 +239,7 @@ export default class ColumnSchema extends BaseUISchema {
filter: (options)=>{
let result = options;
let edit_types = state?.edit_types || [];
if(!obj.isNew(state)) {
if(!obj.isNew(state) && !this.inErd) {
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
}
return result;
@@ -256,7 +256,7 @@ export default class ColumnSchema extends BaseUISchema {
filter: (options)=>{
let result = options;
let edit_types = row?.edit_types || [];
if(!obj.isNew(row)) {
if(!obj.isNew(row) && !this.inErd) {
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
}
return result;
@@ -272,7 +272,10 @@ export default class ColumnSchema extends BaseUISchema {
id: 'inheritedfrom', label: gettext('Inherited from table'),
type: 'text', readonly: true, editable: false,
visible: function() {
return _.isUndefined(this.nodeInfo['table'] || this.nodeInfo['view'] || this.nodeInfo['mview']);
if(this.nodeInfo) {
return _.isUndefined(this.nodeInfo['table'] || this.nodeInfo['view'] || this.nodeInfo['mview']);
}
return false;
},
},{
id: 'attlen', label: gettext('Length/Precision'),
@@ -417,8 +420,7 @@ export default class ColumnSchema extends BaseUISchema {
{'label': gettext('IDENTITY'), 'value': 'i'},
];
if (this.nodeInfo && this.nodeInfo.server &&
this.nodeInfo.server.version >= 120000) {
if (this.getServerVersion() >= 120000) {
// You can't change the existing column to Generated column.
if (this.isNew(state)) {
options.push({
@@ -529,15 +531,18 @@ export default class ColumnSchema extends BaseUISchema {
], null, null, ['name', 'value']),
uniqueCol : ['name'], mode: ['edit', 'create'],
canAdd: true, canEdit: false, canDelete: true,
}, {
},{
id: 'security', label: gettext('Security'), type: 'group',
visible: !this.inErd,
},{
id: 'attacl', label: gettext('Privileges'), type: 'collection',
group: gettext('Security'),
group: 'security',
schema: this.getPrivilegeRoleSchema(['a','r','w','x']),
mode: ['edit'], canAdd: true, canDelete: true,
uniqueCol : ['grantee'],
},{
id: 'seclabels', label: gettext('Security labels'), canAdd: true,
schema: new SecLabelSchema(), group: gettext('Security'),
schema: new SecLabelSchema(), group: 'security',
mode: ['edit', 'create'], editable: false, type: 'collection',
min_version: 90100, canEdit: false, canDelete: true,
uniqueCol : ['provider'],

View File

@@ -86,7 +86,7 @@ class ForeignKeyHeaderSchema extends BaseUISchema {
}
}
class ForeignKeyColumnSchema extends BaseUISchema {
export class ForeignKeyColumnSchema extends BaseUISchema {
constructor() {
super({
local_column: undefined,
@@ -111,7 +111,7 @@ class ForeignKeyColumnSchema extends BaseUISchema {
}
export default class ForeignKeySchema extends BaseUISchema {
constructor(fieldOptions={}, nodeInfo, getColumns, initData={}) {
constructor(fieldOptions={}, nodeInfo, getColumns, initValues={}, inErd=false) {
super({
name: undefined,
reftab: undefined,
@@ -128,7 +128,7 @@ export default class ForeignKeySchema extends BaseUISchema {
autoindex: true,
coveringindex: undefined,
hasindex:undefined,
...initData,
...initValues,
});
this.nodeInfo = nodeInfo;
@@ -136,7 +136,7 @@ export default class ForeignKeySchema extends BaseUISchema {
this.fkHeaderSchema = new ForeignKeyHeaderSchema(fieldOptions, getColumns);
this.fkHeaderSchema.fkObj = this;
this.fkColumnSchema = new ForeignKeyColumnSchema();
this.inErd = inErd;
}
get idAttribute() {
@@ -240,9 +240,7 @@ export default class ForeignKeySchema extends BaseUISchema {
return true;
}
// If we are in table edit mode then
if(obj.inTable) {
return true;
} else if(state.hasindex) {
if(state.hasindex) {
return true;
}
return false;
@@ -252,7 +250,7 @@ export default class ForeignKeySchema extends BaseUISchema {
return {};
}
// If we are in table edit mode
if(obj.inTable) {
if(obj.inTable && !this.inErd) {
if(obj.isNew(state) && obj.top.isNew()) {
return {autoindex: false, coveringindex: ''};
}

View File

@@ -70,7 +70,7 @@ export function getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser) {
}
export class ConstraintsSchema extends BaseUISchema {
constructor(nodeInfo, getFkObj, getExConsObj, otherOptions) {
constructor(nodeInfo, getFkObj, getExConsObj, otherOptions, inErd=false) {
super();
this.nodeInfo = nodeInfo;
this.primaryKeyObj = new PrimaryKeySchema({
@@ -81,13 +81,16 @@ export class ConstraintsSchema extends BaseUISchema {
spcname: otherOptions.spcname,
}, nodeInfo);
this.exConsObj = getExConsObj();
this.inErd = inErd;
}
changeColumnOptions(colOptions) {
this.primaryKeyObj.changeColumnOptions(colOptions);
this.fkObj.changeColumnOptions(colOptions);
this.uniqueConsObj.changeColumnOptions(colOptions);
this.exConsObj.changeColumnOptions(colOptions);
if(!this.inErd) {
this.uniqueConsObj.changeColumnOptions(colOptions);
this.exConsObj.changeColumnOptions(colOptions);
}
}
anyColumnAdded(state) {
@@ -147,20 +150,24 @@ export class ConstraintsSchema extends BaseUISchema {
return {foreign_key: []};
}
}
},{
id: 'check_group', type: 'group', label: gettext('Check'), visible: !this.inErd,
},{
id: 'check_constraint', label: '',
schema: new CheckConstraintSchema(),
editable: false, type: 'collection',
group: gettext('Check'), mode: ['edit', 'create'],
group: 'check_group', mode: ['edit', 'create'],
canEdit: true, canDelete: true, deps:['is_partitioned'],
canAdd: true,
columns : ['name', 'consrc'],
disabled: this.inCatalog,
},{
id: 'unique_group', type: 'group', label: gettext('Unique'), visible: !this.inErd,
},{
id: 'unique_constraint', label: '',
schema: this.uniqueConsObj,
editable: false, type: 'collection',
group: gettext('Unique'), mode: ['edit', 'create'],
group: 'unique_group', mode: ['edit', 'create'],
canEdit: true, canDelete: true, deps:['is_partitioned', 'typname'],
columns : ['name', 'columns'],
disabled: this.inCatalog,
@@ -176,11 +183,13 @@ export class ConstraintsSchema extends BaseUISchema {
return {unique_constraint: []};
}
}
},{
id: 'exclude_group', type: 'group', label: gettext('Exclude'), visible: !this.inErd,
},{
id: 'exclude_constraint', label: '',
schema: this.exConsObj,
editable: false, type: 'collection',
group: gettext('Exclude'), mode: ['edit', 'create'],
group: 'exclude_group', mode: ['edit', 'create'],
canEdit: true, canDelete: true, deps:['is_partitioned'],
columns : ['name', 'columns', 'constraint'],
disabled: this.inCatalog,
@@ -276,7 +285,7 @@ export class LikeSchema extends BaseUISchema {
export default class TableSchema extends BaseUISchema {
constructor(fieldOptions={}, nodeInfo, schemas={}, getPrivilegeRoleSchema=()=>{}, getColumns=()=>[],
getCollations=()=>[], getOperatorClass=()=>[], getAttachTables=()=>[], initValues={}) {
getCollations=()=>[], getOperatorClass=()=>[], getAttachTables=()=>[], initValues={}, inErd=false) {
super({
name: undefined,
oid: undefined,
@@ -307,6 +316,7 @@ export default class TableSchema extends BaseUISchema {
autovacuum_enabled: 'x',
primary_key: [],
foreign_key: [],
partition_keys: [],
partitions: [],
partition_type: 'range',
is_partitioned: false,
@@ -325,6 +335,24 @@ export default class TableSchema extends BaseUISchema {
this.columnsSchema = this.schemas.columns && this.schemas.columns() || {};
this.vacuumSettingsSchema = this.schemas.vacuum_settings && this.schemas.vacuum_settings() || {};
this.partitionKeysObj = new PartitionKeysSchema([], getCollations, getOperatorClass);
this.inErd = inErd;
}
static getErdSupportedData(data) {
let newData = {...data};
const SUPPORTED_KEYS = [
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
'columns', 'primary_key', 'foreign_key',
];
newData = _.pick(newData, SUPPORTED_KEYS);
/* Remove inherited references */
newData.columns = newData.columns.map((c)=>{
delete c.inheritedfromtable;
return c;
});
return newData;
}
get idAttribute() {
@@ -397,9 +425,9 @@ export default class TableSchema extends BaseUISchema {
id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties'],
},{
id: 'relowner', label: gettext('Owner'), type: 'select',
options: this.fieldOptions.relowner, noEmpty: true,
options: this.fieldOptions.relowner, noEmpty: this.inErd ? false : true,
mode: ['properties', 'create', 'edit'], controlProps: {allowClear: false},
readonly: this.inCatalog,
readonly: this.inCatalog, visible: !this.inErd,
},{
id: 'schema', label: gettext('Schema'), type: 'select',
options: this.fieldOptions.schema, noEmpty: true,
@@ -407,7 +435,7 @@ export default class TableSchema extends BaseUISchema {
readonly: this.inCatalog,
},{
id: 'spcname', label: gettext('Tablespace'),
visible: !this.inErd,
mode: ['properties', 'create', 'edit'], deps: ['is_partitioned'],
readonly: this.inCatalog, type: (state)=>{
return {
@@ -421,6 +449,9 @@ export default class TableSchema extends BaseUISchema {
id: 'partition', type: 'group', label: gettext('Partitions'),
mode: ['edit', 'create'], min_version: 100000,
visible: function(state) {
if(this.inErd) {
return false;
}
// Always show in case of create mode
if (obj.isNew(state) || state.is_partitioned)
return true;
@@ -429,7 +460,7 @@ export default class TableSchema extends BaseUISchema {
},{
id: 'is_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
type: 'switch', mode: ['properties', 'create', 'edit'],
min_version: 100000,
min_version: 100000, visible: !this.inErd,
readonly: function(state) {
if (!obj.isNew(state))
return true;
@@ -447,7 +478,7 @@ export default class TableSchema extends BaseUISchema {
type: 'select', group: gettext('Columns'),
deps: ['typname', 'is_partitioned'], mode: ['create', 'edit'],
controlProps: { multiple: true, allowClear: false, placeholder: gettext('Select to inherit from...')},
options: this.fieldOptions.coll_inherits,
options: this.fieldOptions.coll_inherits, visible: !this.inErd,
optionsLoaded: (res)=>obj.inheritedTableList=res,
disabled: (state)=>{
if(state.adding_inherit_cols || state.is_partitioned){
@@ -581,7 +612,18 @@ export default class TableSchema extends BaseUISchema {
deps: ['typname', 'is_partitioned'],
depChange: (state, source, topState, actionObj)=>{
if(source[0] === 'columns') {
obj.changeColumnOptions(state.columns);
/* In ERD, attnum is an imp var for setting the links
Here, attnum is set to max avail value.
*/
let columns = state.columns;
if(actionObj.type === SCHEMA_STATE_ACTIONS.ADD_ROW && this.inErd) {
let lastAttnum = _.maxBy(columns, (c)=>c.attnum)?.attnum;
if(_.isUndefined(lastAttnum) || _.isNull(lastAttnum)) {
lastAttnum = -1;
}
columns[columns.length-1].attnum = lastAttnum + 1;
}
obj.changeColumnOptions(columns);
/* If primary key switch changes, primary key collection need to change */
if(actionObj.path.indexOf('is_primary_key') > -1) {
let tabColPath = _.slice(actionObj.path, 0, -1);
@@ -631,6 +673,7 @@ export default class TableSchema extends BaseUISchema {
},{
id: 'typname', label: gettext('Of type'), type: 'select',
mode: ['properties', 'create', 'edit'], group: 'advanced', deps: ['coll_inherits'],
visible: !this.inErd,
disabled: (state)=>{
if(!obj.inSchemaWithModelCheck(state) && isEmptyString(state.coll_inherits)) {
return false;
@@ -743,6 +786,7 @@ export default class TableSchema extends BaseUISchema {
type: 'nested-fieldset', label: gettext('Like'),
group: 'advanced', mode: ['create'],
schema: new LikeSchema(this.fieldOptions.like_relation),
visible: !this.inErd,
},{
id: 'partition_type', label:gettext('Partition Type'),
editable: false, type: 'select', controlProps: {allowClear: false},
@@ -774,6 +818,7 @@ export default class TableSchema extends BaseUISchema {
id: 'partition_keys', label:gettext('Partition Keys'),
schema: obj.partitionKeysObj,
editable: true, type: 'collection',
columns: ['key_type', 'pt_column', 'expression'].concat(!this.inErd ? ['collationame', 'op_class'] : []),
group: 'partition', mode: ['create'],
deps: ['is_partitioned', 'partition_type', 'typname'],
canEdit: false, canDelete: true,
@@ -885,28 +930,32 @@ export default class TableSchema extends BaseUISchema {
'</li></ul>',
].join(''),
min_version: 100000,
},
{
},{
type: 'group', id: 'parameters', label: gettext('Parameters'),
visible: !this.inErd,
},{
// Here - we will create tab control for storage parameters
// (auto vacuum).
type: 'nested-tab', group: gettext('Parameters'),
type: 'nested-tab', group: 'parameters',
mode: ['edit', 'create'], deps: ['is_partitioned'],
schema: this.vacuumSettingsSchema,
schema: this.vacuumSettingsSchema, visible: !this.inErd,
},{
id: 'security_group', type: 'group', label: gettext('Security'), visible: !this.inErd,
},
{
id: 'relacl_str', label: gettext('Privileges'), disabled: this.inCatalog,
type: 'text', mode: ['properties'], group: gettext('Security'),
type: 'text', mode: ['properties'], group: 'security_group',
},
{
id: 'relacl', label: gettext('Privileges'), type: 'collection',
group: gettext('Security'), schema: this.getPrivilegeRoleSchema(['a','r','w','d','D','x','t']),
group: 'security_group', schema: this.getPrivilegeRoleSchema(['a','r','w','d','D','x','t']),
mode: ['edit', 'create'], canAdd: true, canDelete: true,
uniqueCol : ['grantee'],
},{
id: 'seclabels', label: gettext('Security labels'), canEdit: false,
schema: new SecLabelSchema(), editable: false, canAdd: true,
type: 'collection', min_version: 90100, mode: ['edit', 'create'],
group: gettext('Security'), canDelete: true, control: 'unique-col-collection',
group: 'security_group', canDelete: true, control: 'unique-col-collection',
},{
id: 'vacuum_settings_str', label: gettext('Storage settings'),
type: 'multiline', group: 'advanced', mode: ['properties'],

View File

@@ -1125,7 +1125,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
elif part_type in data and data[part_type] == 'hash':
partition_scheme = 'HASH ('
for row in data[part_keys]:
for row in data.get(part_keys, []):
if row['key_type'] == 'column':
partition_scheme += self.qtIdent(
self.conn, row['pt_column'])
@@ -1141,7 +1141,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
partition_scheme += row['expression'] + ', '
# Remove extra space and comma
if len(data[part_keys]) > 0:
if len(data.get(part_keys, [])) > 0:
partition_scheme = partition_scheme[:-2]
partition_scheme += ')'