mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Port Table, Column, Primary key, Foreign key, Check constraint, Unique constraint, Exclusion constraint.
This commit is contained in:
parent
725d9b4bbf
commit
261cec1d20
@ -7,6 +7,8 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodeColumnSchema } from './column.ui';
|
||||
|
||||
define('pgadmin.node.column', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
@ -28,68 +30,6 @@ define('pgadmin.node.column', [
|
||||
});
|
||||
}
|
||||
|
||||
// This Node model will be used for variable control for column
|
||||
var VariablesModel = Backform.VariablesModel = pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'name',
|
||||
defaults: {
|
||||
name: null,
|
||||
value: null,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'select2',
|
||||
type: 'text', disabled: false, node: 'column',
|
||||
options: [['n_distinct', 'n_distinct'],
|
||||
['n_distinct_inherited','n_distinct_inherited']],
|
||||
select2: {placeholder: gettext('Select variable')},
|
||||
cellHeaderClasses:'width_percent_50',
|
||||
},{
|
||||
id: 'value', label: gettext('Value'),
|
||||
type: 'text', disabled: false,
|
||||
cellHeaderClasses:'width_percent_50',
|
||||
}],
|
||||
validate: function() {
|
||||
if (
|
||||
_.isUndefined(this.get('value')) ||
|
||||
_.isNull(this.get('value')) ||
|
||||
String(this.get('value')).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
var errmsg = gettext('Please provide input for variable.');
|
||||
this.errorModel.set('value', errmsg);
|
||||
return errmsg;
|
||||
} else {
|
||||
this.errorModel.unset('value');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Integer Cell for Columns Length and Precision
|
||||
var IntegerDepCell = Backgrid.Extension.IntegerDepCell =
|
||||
Backgrid.IntegerCell.extend({
|
||||
initialize: function() {
|
||||
Backgrid.NumberCell.prototype.initialize.apply(this, arguments);
|
||||
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
dependentChanged: function () {
|
||||
this.model.set(this.column.get('name'), null);
|
||||
this.render();
|
||||
return this;
|
||||
},
|
||||
render: function() {
|
||||
Backgrid.NumberCell.prototype.render.apply(this, arguments);
|
||||
|
||||
var model = this.model,
|
||||
column = this.column,
|
||||
editable = this.column.get('editable'),
|
||||
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
|
||||
|
||||
if (is_editable){ this.$el.addClass('editable'); }
|
||||
else { this.$el.removeClass('editable'); }
|
||||
return this;
|
||||
},
|
||||
remove: Backgrid.Extension.DependentCell.prototype.remove,
|
||||
});
|
||||
|
||||
if (!pgBrowser.Nodes['column']) {
|
||||
pgBrowser.Nodes['column'] = pgBrowser.Node.extend({
|
||||
parent_type: ['table', 'view', 'mview'],
|
||||
@ -155,690 +95,30 @@ define('pgadmin.node.column', [
|
||||
},
|
||||
]);
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'attnum',
|
||||
|
||||
defaults: {
|
||||
name: undefined,
|
||||
attowner: undefined,
|
||||
atttypid: undefined,
|
||||
attnum: undefined,
|
||||
cltype: undefined,
|
||||
collspcname: undefined,
|
||||
attacl: undefined,
|
||||
description: undefined,
|
||||
parent_tbl: undefined,
|
||||
min_val_attlen: undefined,
|
||||
min_val_attprecision: undefined,
|
||||
max_val_attlen: undefined,
|
||||
max_val_attprecision: undefined,
|
||||
edit_types: undefined,
|
||||
is_primary_key: false,
|
||||
inheritedfrom: undefined,
|
||||
attstattarget:undefined,
|
||||
attnotnull: false,
|
||||
attlen: null,
|
||||
attprecision: null,
|
||||
attidentity: 'a',
|
||||
seqincrement: undefined,
|
||||
seqstart: undefined,
|
||||
seqmin: undefined,
|
||||
seqmax: undefined,
|
||||
seqcache: undefined,
|
||||
seqcycle: undefined,
|
||||
colconstype: 'n',
|
||||
genexpr: undefined,
|
||||
},
|
||||
initialize: function(attrs) {
|
||||
if (_.size(attrs) !== 0) {
|
||||
this.set({
|
||||
'old_attidentity': this.get('attidentity'),
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchemaWithColumnCheck',
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
editable: 'editable_check_for_table',
|
||||
},{
|
||||
// Need to show this field only when creating new table
|
||||
// [in SubNode control]
|
||||
id: 'is_primary_key', label: gettext('Primary key?'),
|
||||
cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
|
||||
deps:['name'], cellHeaderClasses:'width_percent_5',
|
||||
options: {
|
||||
onText: gettext('Yes'), offText: gettext('No'),
|
||||
onColor: 'success', offColor: 'ternary',
|
||||
},
|
||||
visible: function(m) {
|
||||
return _.isUndefined(
|
||||
m.top.node_info['table'] || m.top.node_info['view'] ||
|
||||
m.top.node_info['mview']
|
||||
);
|
||||
},
|
||||
disabled: function(m){
|
||||
// Disable it, when one of this:
|
||||
// - Primary key already exist
|
||||
// - Table is a partitioned table
|
||||
if (
|
||||
m.top && ((
|
||||
!_.isUndefined(m.top.get('oid')) &&
|
||||
!_.isUndefined(m.top.get('primary_key')) &&
|
||||
m.top.get('primary_key').length > 0 &&
|
||||
!_.isUndefined(m.top.get('primary_key').first().get('oid'))
|
||||
) || (
|
||||
m.top.has('is_partitioned') && m.top.get('is_partitioned') &&
|
||||
m.top.node_info.server && m.top.node_info.server.version < 11000
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var name = m.get('name');
|
||||
|
||||
if(!m.inSchemaWithColumnCheck.apply(this, [m]) &&
|
||||
(_.isUndefined(name) || _.isNull(name) || name == '')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
editable: function(m) {
|
||||
// If HeaderCell then allow True
|
||||
if(m instanceof Backbone.Collection) {
|
||||
return true;
|
||||
}
|
||||
// If primary key already exist then disable.
|
||||
if (m.top && !_.isUndefined(m.top.get('oid')) &&
|
||||
!_.isUndefined(m.top.get('primary_key')) &&
|
||||
m.top.get('primary_key').length > 0 &&
|
||||
!_.isUndefined(m.top.get('primary_key').first().get('oid'))) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If table is partitioned table then disable
|
||||
if (m.top && !_.isUndefined(m.top.get('is_partitioned')) &&
|
||||
m.top.get('is_partitioned') && m.top.node_info.server &&
|
||||
m.top.node_info.server.version < 11000)
|
||||
{
|
||||
setTimeout(function () {
|
||||
m.set('is_primary_key', false);
|
||||
}, 10);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!m.inSchemaWithColumnCheck.apply(this, [m])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'attnum', label: gettext('Position'), cell: 'string',
|
||||
type: 'text', disabled: 'notInSchema', mode: ['properties'],
|
||||
},{
|
||||
id: 'cltype', label: gettext('Data type'),
|
||||
cell: Backgrid.Extension.NodeAjaxOptionsCell,
|
||||
type: 'text', disabled: 'inSchemaWithColumnCheck',
|
||||
control: 'node-ajax-options', url: 'get_types', node: 'table',
|
||||
cellHeaderClasses:'width_percent_30', first_empty: true,
|
||||
select2: { allowClear: false }, group: gettext('Definition'),
|
||||
cache_node: 'table',
|
||||
transform: function(data, cell) {
|
||||
/* 'transform' function will be called by control, and cell both.
|
||||
* The way, we use the transform in cell, and control is different.
|
||||
* Because - options are shared using 'column' object in backgrid,
|
||||
* hence - the cell is passed as second parameter, while the control
|
||||
* uses (this) as a object.
|
||||
*/
|
||||
var control = cell || this,
|
||||
m = control.model;
|
||||
|
||||
/* We need different data in create mode & in edit mode
|
||||
* if we are in create mode then return data as it is
|
||||
* if we are in edit mode then we need to filter data
|
||||
*/
|
||||
control.model.datatypes = data;
|
||||
var edit_types = m.get('edit_types'),
|
||||
result = [];
|
||||
|
||||
// If called from Table, We will check if in edit mode
|
||||
// then send edit_types only
|
||||
if( !_.isUndefined(m.top) && !m.top.isNew() ) {
|
||||
_.each(data, function(t) {
|
||||
if (_.indexOf(edit_types, t.value) != -1) {
|
||||
result.push(t);
|
||||
}
|
||||
});
|
||||
// There may be case that user adds new column in existing collection
|
||||
// we will not have edit types then
|
||||
return result.length > 0 ? result : data;
|
||||
}
|
||||
|
||||
// If called from Column
|
||||
if(m.isNew()) {
|
||||
return data;
|
||||
} else {
|
||||
//edit mode
|
||||
_.each(data, function(t) {
|
||||
if (_.indexOf(edit_types, t.value) != -1) {
|
||||
result.push(t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
editable: 'editable_check_for_table',
|
||||
},{
|
||||
// Need to show this field only when creating new table [in SubNode control]
|
||||
id: 'inheritedfrom', label: gettext('Inherited from table'),
|
||||
type: 'text', readonly: true, editable: false,
|
||||
cellHeaderClasses:'width_percent_10',
|
||||
visible: function(m) {
|
||||
return _.isUndefined(m.top.node_info['table'] || m.top.node_info['view'] || m.top.node_info['mview']);
|
||||
},
|
||||
},{
|
||||
id: 'attlen', label: gettext('Length/Precision'), cell: IntegerDepCell,
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), cellHeaderClasses:'width_percent_20',
|
||||
disabled: function(m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = true;
|
||||
_.each(m.datatypes, function(o) {
|
||||
if ( of_type == o.value ) {
|
||||
if(o.length)
|
||||
{
|
||||
m.set('min_val_attlen', o.min_val, {silent: true});
|
||||
m.set('max_val_attlen', o.max_val, {silent: true});
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
flag && setTimeout(function() {
|
||||
if(m.get('attlen')) {
|
||||
m.set('attlen', null);
|
||||
}
|
||||
},10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
editable: function(m) {
|
||||
// inheritedfrom has value then we should disable it
|
||||
if(!_.isUndefined(m.get('inheritedfrom'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m.datatypes) {
|
||||
// datatypes not loaded, may be this call is from CallByNeed from backgrid cell initialize.
|
||||
return true;
|
||||
}
|
||||
var of_type = m.get('cltype'),
|
||||
flag = false;
|
||||
|
||||
_.each(m.datatypes, function(o) {
|
||||
if ( of_type == o.value ) {
|
||||
if(o.length) {
|
||||
m.set('min_val_attlen', o.min_val, {silent: true});
|
||||
m.set('max_val_attlen', o.max_val, {silent: true});
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
!flag && setTimeout(function() {
|
||||
if(m.get('attlen')) {
|
||||
m.set('attlen', null, {silent: true});
|
||||
}
|
||||
},10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
},{
|
||||
id: 'attprecision', label: gettext('Scale'), cell: IntegerDepCell,
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), cellHeaderClasses:'width_percent_20',
|
||||
disabled: function(m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = true;
|
||||
_.each(m.datatypes, function(o) {
|
||||
if ( of_type == o.value ) {
|
||||
if(o.precision) {
|
||||
m.set('min_val_attprecision', 0, {silent: true});
|
||||
m.set('max_val_attprecision', o.max_val, {silent: true});
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
flag && setTimeout(function() {
|
||||
if(m.get('attprecision')) {
|
||||
m.set('attprecision', null);
|
||||
}
|
||||
},10);
|
||||
return flag;
|
||||
},
|
||||
editable: function(m) {
|
||||
// inheritedfrom has value then we should disable it
|
||||
if(!_.isUndefined(m.get('inheritedfrom'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m.datatypes) {
|
||||
// datatypes not loaded yet, may be this call is from CallByNeed from backgrid cell initialize.
|
||||
return true;
|
||||
}
|
||||
|
||||
var of_type = m.get('cltype'),
|
||||
flag = false;
|
||||
_.each(m.datatypes, function(o) {
|
||||
if ( of_type == o.value ) {
|
||||
if(o.precision) {
|
||||
m.set('min_val_attprecision', 0, {silent: true});
|
||||
m.set('max_val_attprecision', o.max_val, {silent: true});
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
!flag && setTimeout(function() {
|
||||
if(m.get('attprecision')) {
|
||||
m.set('attprecision', null);
|
||||
}
|
||||
},10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
},{
|
||||
id: 'collspcname', label: gettext('Collation'), cell: 'string',
|
||||
type: 'text', control: 'node-ajax-options', url: 'get_collations',
|
||||
group: gettext('Definition'), node: 'collation',
|
||||
deps: ['cltype'], disabled: function(m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = true;
|
||||
_.each(m.datatypes, function(o) {
|
||||
if ( of_type == o.value ) {
|
||||
if(o.is_collatable)
|
||||
{
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (flag) {
|
||||
setTimeout(function(){
|
||||
if(m.get('collspcname') && m.get('collspcname') !== '') {
|
||||
m.set('collspcname', '');
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
},{
|
||||
id: 'attstattarget', label: gettext('Statistics'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchemaWithColumnCheck', mode: ['properties', 'edit'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'attstorage', label: gettext('Storage'), group: gettext('Definition'),
|
||||
type: 'text', mode: ['properties', 'edit'],
|
||||
cell: 'string', disabled: 'inSchemaWithColumnCheck', first_empty: true,
|
||||
control: 'select2', select2: { placeholder: gettext('Select storage'),
|
||||
allowClear: false,
|
||||
width: '100%',
|
||||
},
|
||||
options: [
|
||||
{label: 'PLAIN', value: 'p'},
|
||||
{label: 'MAIN', value: 'm'},
|
||||
{label: 'EXTERNAL', value: 'e'},
|
||||
{label: 'EXTENDED', value: 'x'},
|
||||
],
|
||||
},{
|
||||
id: 'defval', label: gettext('Default'), cell: 'string',
|
||||
type: 'text', group: gettext('Constraints'), deps: ['cltype', 'colconstype'],
|
||||
disabled: function(m) {
|
||||
var is_disabled = false;
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
var type = m.get('cltype');
|
||||
is_disabled = (type == 'serial' || type == 'bigserial' || type == 'smallserial');
|
||||
}
|
||||
|
||||
is_disabled = is_disabled || m.get('colconstype') != 'n';
|
||||
if (is_disabled && m.isNew()) {
|
||||
setTimeout(function () {
|
||||
m.set('defval', undefined);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return is_disabled;
|
||||
},
|
||||
},{
|
||||
id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
|
||||
type: 'switch', cellHeaderClasses:'width_percent_20',
|
||||
group: gettext('Constraints'), editable: 'editable_check_for_table',
|
||||
options: { onText: gettext('Yes'), offText: gettext('No'), onColor: 'success', offColor: 'ternary' },
|
||||
deps: ['colconstype'],
|
||||
disabled: function(m) {
|
||||
if (m.get('colconstype') == 'i') {
|
||||
setTimeout(function () {
|
||||
m.set('attnotnull', true);
|
||||
}, 10);
|
||||
}
|
||||
return m.inSchemaWithColumnCheck(m);
|
||||
},
|
||||
}, {
|
||||
id: 'colconstype',
|
||||
label: gettext('Type'),
|
||||
cell: 'string',
|
||||
type: 'radioModern',
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-9',
|
||||
controlLabelClassName: 'control-label col-sm-3 col-12',
|
||||
group: gettext('Constraints'),
|
||||
options: function(m) {
|
||||
var opt_array = [
|
||||
{'label': gettext('NONE'), 'value': 'n'},
|
||||
{'label': gettext('IDENTITY'), 'value': 'i'},
|
||||
];
|
||||
|
||||
if (m.top.node_info && m.top.node_info.server &&
|
||||
m.top.node_info.server.version >= 120000) {
|
||||
// You can't change the existing column to Generated column.
|
||||
if (m.isNew()) {
|
||||
opt_array.push({
|
||||
'label': gettext('GENERATED'),
|
||||
'value': 'g',
|
||||
});
|
||||
} else {
|
||||
opt_array.push({
|
||||
'label': gettext('GENERATED'),
|
||||
'value': 'g',
|
||||
'disabled': true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return opt_array;
|
||||
},
|
||||
disabled: function(m) {
|
||||
if (!m.isNew() && m.get('colconstype') == 'g') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
visible: function(m) {
|
||||
if (m.top.node_info && m.top.node_info.server &&
|
||||
m.top.node_info.server.version >= 100000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}, {
|
||||
id: 'attidentity', label: gettext('Identity'), control: 'select2',
|
||||
cell: 'select2',
|
||||
select2: {placeholder: gettext('Select identity'), allowClear: false, width: '100%'},
|
||||
min_version: 100000, group: gettext('Constraints'),
|
||||
'options': [
|
||||
{label: gettext('ALWAYS'), value: 'a'},
|
||||
{label: gettext('BY DEFAULT'), value: 'd'},
|
||||
],
|
||||
deps: ['colconstype'], visible: 'isTypeIdentity',
|
||||
disabled: function(m) {
|
||||
if (!m.isNew()) {
|
||||
if (m.get('attidentity') == '' && m.get('colconstype') == 'i') {
|
||||
setTimeout(function () {
|
||||
m.set('attidentity', m.get('old_attidentity'));
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}, {
|
||||
id: 'seqincrement', label: gettext('Increment'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqstart', label: gettext('Start'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
disabled: 'isIdentityColumn', deps: ['attidentity', 'colconstype'],
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqmin', label: gettext('Minimum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqmax', label: gettext('Maximum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqcache', label: gettext('Cache'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqcycle', label: gettext('Cycled'), type: 'switch',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'genexpr', label: gettext('Expression'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min_version: 120000, deps: ['colconstype'], visible: 'isTypeGenerated',
|
||||
readonly: function(m) {
|
||||
return !m.isNew();
|
||||
},
|
||||
},{
|
||||
id: 'is_pk', label: gettext('Primary key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'is_fk', label: gettext('Foreign key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'is_inherited', label: gettext('Inherited?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'tbls_inherited', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', mode: ['properties'], deps: ['is_inherited'],
|
||||
group: gettext('Definition'),
|
||||
visible: function(m) {
|
||||
return (!_.isUndefined(m.get('is_inherited')) && m.get('is_inherited'));
|
||||
},
|
||||
},{
|
||||
id: 'is_sys_column', label: gettext('System column?'), cell: 'string',
|
||||
type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'notInSchema',
|
||||
},{
|
||||
id: 'attoptions', label: gettext('Variables'), type: 'collection',
|
||||
group: gettext('Variables'), control: 'unique-col-collection',
|
||||
model: VariablesModel, uniqueCol : ['name'],
|
||||
mode: ['edit', 'create'], canAdd: true, canEdit: false,
|
||||
canDelete: true,
|
||||
}, pgBrowser.SecurityGroupSchema, {
|
||||
id: 'attacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: 'security', control: 'unique-col-collection',
|
||||
model: pgBrowser.Node.PrivilegeRoleModel.extend({
|
||||
privileges: ['a','r','w','x']}),
|
||||
mode: ['edit'], canAdd: true, canDelete: true,
|
||||
uniqueCol : ['grantee'],
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'), canAdd: true,
|
||||
model: pgBrowser.SecLabelModel, group: 'security',
|
||||
mode: ['edit', 'create'], editable: false, type: 'collection',
|
||||
min_version: 90100, canEdit: false, canDelete: true,
|
||||
control: 'unique-col-collection',
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg = undefined;
|
||||
|
||||
// Nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
this.errorModel.clear();
|
||||
return null;
|
||||
} else {
|
||||
this.errorModel.clear();
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('name'))
|
||||
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Column name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('cltype'))
|
||||
|| String(this.get('cltype')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Column type cannot be empty.');
|
||||
this.errorModel.set('cltype', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.get('cltype'))
|
||||
&& !_.isUndefined(this.get('attlen'))
|
||||
&& !_.isNull(this.get('attlen'))
|
||||
&& this.get('attlen') !== '') {
|
||||
// Validation for Length field
|
||||
if (this.get('attlen') < this.get('min_val_attlen'))
|
||||
msg = gettext('Length/Precision should not be less than: ') + this.get('min_val_attlen');
|
||||
if (this.get('attlen') > this.get('max_val_attlen'))
|
||||
msg = gettext('Length/Precision should not be greater than: ') + this.get('max_val_attlen');
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
this.errorModel.set('attlen', msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.get('cltype'))
|
||||
&& !_.isUndefined(this.get('attprecision'))
|
||||
&& !_.isNull(this.get('attprecision'))
|
||||
&& this.get('attprecision') !== '') {
|
||||
// Validation for precision field
|
||||
if (this.get('attprecision') < this.get('min_val_attprecision'))
|
||||
msg = gettext('Scale should not be less than: ') + this.get('min_val_attprecision');
|
||||
if (this.get('attprecision') > this.get('max_val_attprecision'))
|
||||
msg = gettext('Scale should not be greater than: ') + this.get('max_val_attprecision');
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
this.errorModel.set('attprecision', msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
let genexpr = this.get('genexpr');
|
||||
if (this.get('colconstype') == 'g' &&
|
||||
(_.isUndefined(genexpr) || _.isNull(genexpr) || genexpr == '')) {
|
||||
msg = gettext('Expression value cannot be empty.');
|
||||
this.errorModel.set('genexpr', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('genexpr');
|
||||
}
|
||||
|
||||
var minimum = this.get('seqmin'),
|
||||
maximum = this.get('seqmax'),
|
||||
start = this.get('seqstart');
|
||||
|
||||
if (!this.isNew() && this.get('colconstype') == 'i' &&
|
||||
(this.get('old_attidentity') == 'a' || this.get('old_attidentity') == 'd') &&
|
||||
(this.get('attidentity') == 'a' || this.get('attidentity') == 'd')) {
|
||||
if (_.isUndefined(this.get('seqincrement'))
|
||||
|| String(this.get('seqincrement')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Increment value cannot be empty.');
|
||||
this.errorModel.set('seqincrement', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqincrement');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqmin'))
|
||||
|| String(this.get('seqmin')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Minimum value cannot be empty.');
|
||||
this.errorModel.set('seqmin', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqmin');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqmax'))
|
||||
|| String(this.get('seqmax')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Maximum value cannot be empty.');
|
||||
this.errorModel.set('seqmax', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqmax');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqcache'))
|
||||
|| String(this.get('seqcache')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Cache value cannot be empty.');
|
||||
this.errorModel.set('seqcache', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqcache');
|
||||
}
|
||||
}
|
||||
var min_lt = gettext('Minimum value must be less than maximum value.'),
|
||||
start_lt = gettext('Start value cannot be less than minimum value.'),
|
||||
start_gt = gettext('Start value cannot be greater than maximum value.');
|
||||
|
||||
if (_.isEmpty(minimum) || _.isEmpty(maximum))
|
||||
return null;
|
||||
|
||||
if ((minimum == 0 && maximum == 0) ||
|
||||
(parseInt(minimum, 10) >= parseInt(maximum, 10))) {
|
||||
this.errorModel.set('seqmin', min_lt);
|
||||
return min_lt;
|
||||
} else {
|
||||
this.errorModel.unset('seqmin');
|
||||
}
|
||||
|
||||
if (start && minimum && parseInt(start) < parseInt(minimum)) {
|
||||
this.errorModel.set('seqstart', start_lt);
|
||||
return start_lt;
|
||||
} else {
|
||||
this.errorModel.unset('seqstart');
|
||||
}
|
||||
|
||||
if (start && maximum && parseInt(start) > parseInt(maximum)) {
|
||||
this.errorModel.set('seqstart', start_gt);
|
||||
return start_gt;
|
||||
} else {
|
||||
this.errorModel.unset('seqstart');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
// Check whether the column is identity column or not
|
||||
isIdentityColumn: function(m) {
|
||||
let isIdentity = m.get('attidentity');
|
||||
if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
// Check whether the column is a identity column
|
||||
isTypeIdentity: function(m) {
|
||||
let colconstype = m.get('colconstype');
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'i') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// Check whether the column is a generated column
|
||||
isTypeGenerated: function(m) {
|
||||
let colconstype = m.get('colconstype');
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'g') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
notInSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
@ -847,54 +127,6 @@ define('pgadmin.node.column', [
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
inSchemaWithModelCheck: function(m) {
|
||||
if(this.node_info && 'schema' in this.node_info)
|
||||
{
|
||||
// We will disable control if it's in 'edit' mode
|
||||
return !(m.isNew());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Checks weather to enable/disable control
|
||||
inSchemaWithColumnCheck: function(m) {
|
||||
var node_info = this.node_info || m.node_info || m.top.node_info;
|
||||
|
||||
// disable all fields if column is listed under view or mview
|
||||
if (node_info && ('view' in node_info || 'mview' in node_info)) {
|
||||
if (this && _.has(this, 'name') && (this.name != 'defval')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(node_info && 'schema' in node_info)
|
||||
{
|
||||
// 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
|
||||
if(!_.isUndefined(m.get('inheritedfrom'))) {
|
||||
return true;
|
||||
}
|
||||
// ie: it's position is less then 1
|
||||
if (m.isNew()) {
|
||||
return false;
|
||||
}
|
||||
// if we are in edit mode
|
||||
return !(!_.isUndefined(m.get('attnum')) && m.get('attnum') > 0 );
|
||||
}
|
||||
return true;
|
||||
},
|
||||
editable_check_for_table: function(arg) {
|
||||
if (arg instanceof Backbone.Collection) {
|
||||
return !arg.model.prototype.inSchemaWithColumnCheck.apply(
|
||||
this, [arg.top]
|
||||
);
|
||||
} else {
|
||||
return !arg.inSchemaWithColumnCheck.apply(
|
||||
this, [arg]
|
||||
);
|
||||
}
|
||||
},
|
||||
}),
|
||||
// Below function will enable right click menu for creating column
|
||||
canCreate: function(itemData, item, data) {
|
||||
|
@ -0,0 +1,613 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import VariableSchema from 'top/browser/server_groups/servers/static/js/variable.ui';
|
||||
import SecLabelSchema from 'top/browser/server_groups/servers/static/js/sec_label.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from '../../../../../../../../../static/js/validators';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../../static/js/privilege.ui';
|
||||
import { getNodeAjaxOptions } from '../../../../../../../../static/js/node_ajax';
|
||||
|
||||
export function getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
||||
return new ColumnSchema(
|
||||
(privileges)=>getNodePrivilegeRoleSchema(this, treeNodeInfo, itemNodeData, privileges),
|
||||
treeNodeInfo,
|
||||
()=>getNodeAjaxOptions('get_types', pgBrowser.Nodes['table'], treeNodeInfo, itemNodeData, {
|
||||
cacheLevel: 'table',
|
||||
}),
|
||||
()=>getNodeAjaxOptions('get_collations', pgBrowser.Nodes['collation'], treeNodeInfo, itemNodeData),
|
||||
);
|
||||
}
|
||||
|
||||
export default class ColumnSchema extends BaseUISchema {
|
||||
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions) {
|
||||
super({
|
||||
name: undefined,
|
||||
attowner: undefined,
|
||||
atttypid: undefined,
|
||||
attnum: undefined,
|
||||
cltype: undefined,
|
||||
collspcname: undefined,
|
||||
attacl: undefined,
|
||||
description: undefined,
|
||||
parent_tbl: undefined,
|
||||
min_val_attlen: undefined,
|
||||
min_val_attprecision: undefined,
|
||||
max_val_attlen: undefined,
|
||||
max_val_attprecision: undefined,
|
||||
edit_types: undefined,
|
||||
is_primary_key: false,
|
||||
inheritedfrom: undefined,
|
||||
attstattarget:undefined,
|
||||
attnotnull: false,
|
||||
attlen: null,
|
||||
attprecision: null,
|
||||
attidentity: 'a',
|
||||
attoptions: [],
|
||||
seqincrement: undefined,
|
||||
seqstart: undefined,
|
||||
seqmin: undefined,
|
||||
seqmax: undefined,
|
||||
seqcache: undefined,
|
||||
seqcycle: undefined,
|
||||
colconstype: 'n',
|
||||
genexpr: undefined,
|
||||
});
|
||||
|
||||
this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.cltypeOptions = cltypeOptions;
|
||||
this.collspcnameOptions = collspcnameOptions;
|
||||
|
||||
this.datatypes = [];
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'attnum';
|
||||
}
|
||||
|
||||
inSchemaWithColumnCheck(state) {
|
||||
// disable all fields if column is listed under view or mview
|
||||
if (this.nodeInfo && ('view' in this.nodeInfo || 'mview' in this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if('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
|
||||
if(!_.isUndefined(state.inheritedfrom)) {
|
||||
return true;
|
||||
}
|
||||
// ie: it's position is less than 1
|
||||
if(!_.isUndefined(state.attnum) && state.attnum <= 0) {
|
||||
return true;
|
||||
}
|
||||
// if we are in edit mode
|
||||
return !this.isNew(state);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
editableCheckForTable(state) {
|
||||
return !this.inSchemaWithColumnCheck(state);
|
||||
}
|
||||
|
||||
// Check whether the column is identity column or not
|
||||
isIdentityColumn(state) {
|
||||
let isIdentity = state.attidentity;
|
||||
if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether the column is a identity column
|
||||
isTypeIdentity(state) {
|
||||
let colconstype = state.colconstype;
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'i') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether the column is a generated column
|
||||
isTypeGenerated(state) {
|
||||
let colconstype = state.colconstype;
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'g') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
inSchemaWithModelCheck(state) {
|
||||
if(this.nodeInfo && 'schema' in this.nodeInfo)
|
||||
{
|
||||
// We will disable control if it's in 'edit' mode
|
||||
return !(this.isNew(state));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
attlenRange(state) {
|
||||
for(let o of this.datatypes) {
|
||||
if ( state.cltype == o.value ) {
|
||||
if(o.length) return {min: o.min_val || 0, max: o.max_val};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
attprecisionRange(state) {
|
||||
for(let o of this.datatypes) {
|
||||
if ( state.cltype == o.value ) {
|
||||
if(o.precision) return {min: o.min_val || 0, max: o.max_val};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), cell: 'text',
|
||||
type: 'text', disabled: obj.inSchemaWithColumnCheck,
|
||||
editable: this.editableCheckForTable, noEmpty: true,
|
||||
minWidth: 115,
|
||||
},{
|
||||
// Need to show this field only when creating new table
|
||||
// [in SubNode control]
|
||||
id: 'is_primary_key', label: gettext('Primary key?'),
|
||||
cell: 'switch', type: 'switch', minWidth: 100, deps:['name'],
|
||||
visible: ()=>{
|
||||
return _.isUndefined(
|
||||
this.nodeInfo['table'] || this.nodeInfo['view'] ||
|
||||
this.nodeInfo['mview']
|
||||
);
|
||||
},
|
||||
disabled: (state)=>{
|
||||
// Disable it, when one of this:
|
||||
// - Primary key already exist
|
||||
// - Table is a partitioned table
|
||||
if (
|
||||
obj.top && ((
|
||||
!_.isUndefined(obj.top.origData['oid'])
|
||||
&& !_.isUndefined(obj.top.origData['primary_key'])
|
||||
&& obj.top.origData['primary_key'].length > 0
|
||||
&& !_.isUndefined(obj.top.origData['primary_key'][0]['oid'])
|
||||
) || (
|
||||
'is_partitioned' in obj.top.origData
|
||||
&& obj.top.origData['is_partitioned']
|
||||
&& obj.nodeInfo.server && obj.nodeInfo.server.version < 11000
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var name = state.name;
|
||||
|
||||
if(!obj.inSchemaWithColumnCheck(state)
|
||||
&& (_.isUndefined(name) || _.isNull(name) || name == '')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
editable: function(state) {
|
||||
// If primary key already exist then disable.
|
||||
if (
|
||||
obj.top && ((
|
||||
!_.isUndefined(obj.top.origData['oid'])
|
||||
&& !_.isUndefined(obj.top.origData['primary_key'])
|
||||
&& obj.top.origData['primary_key'].length > 0
|
||||
&& !_.isUndefined(obj.top.origData['primary_key'][0]['oid'])
|
||||
))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If table is partitioned table then disable
|
||||
if(
|
||||
'is_partitioned' in obj.top.origData
|
||||
&& obj.top.origData['is_partitioned']
|
||||
&& obj.getServerVersion() < 11000
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!obj.inSchemaWithColumnCheck(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'attnum', label: gettext('Position'), cell: 'text',
|
||||
type: 'text', disabled: this.inCatalog, mode: ['properties'],
|
||||
},{
|
||||
id: 'cltype', label: gettext('Data type'),
|
||||
disabled: obj.inSchemaWithColumnCheck, minWidth: 150,
|
||||
group: gettext('Definition'), noEmpty: true,
|
||||
editable: this.editableCheckForTable,
|
||||
options: this.cltypeOptions, optionsLoaded: (options)=>{obj.datatypes = options;},
|
||||
type: (state)=>{
|
||||
return {
|
||||
type: 'select',
|
||||
options: this.cltypeOptions,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options)=>{
|
||||
let result = options;
|
||||
let edit_types = state?.edit_types || [];
|
||||
if(!obj.isNew(state)) {
|
||||
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
cell: (row)=>{
|
||||
return {
|
||||
cell: 'select',
|
||||
options: this.cltypeOptions,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options)=>{
|
||||
let result = options;
|
||||
let edit_types = row?.edit_types || [];
|
||||
if(!obj.isNew(row)) {
|
||||
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
},{
|
||||
/* This field is required to send it to back end */
|
||||
id: 'inheritedid', label: gettext(''), type: 'text', visible: false,
|
||||
},{
|
||||
// Need to show this field only when creating new table [in SubNode control]
|
||||
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']);
|
||||
},
|
||||
},{
|
||||
id: 'attlen', label: gettext('Length/Precision'), cell: 'int',
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), width: 120, minWidth: 120,
|
||||
depChange: (state)=>{
|
||||
let range = this.attlenRange(state);
|
||||
if(range) {
|
||||
return {
|
||||
...state, min_val_attlen: range.min, max_val_attlen: range.max,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state, attlen: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
return !obj.attlenRange(state);
|
||||
},
|
||||
editable: function(state) {
|
||||
// inheritedfrom has value then we should disable it
|
||||
if (!isEmptyString(state.inheritedfrom)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(obj.attlenRange(state));
|
||||
},
|
||||
},{
|
||||
id: 'attprecision', label: gettext('Scale'), cell: 'int', minWidth: 60,
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'),
|
||||
depChange: (state)=>{
|
||||
let range = this.attprecisionRange(state);
|
||||
if(range) {
|
||||
return {
|
||||
...state, min_val_attprecision: range.min, max_val_attprecision: range.max,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state, attprecision: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
return !this.attprecisionRange(state);
|
||||
},
|
||||
editable: function(state) {
|
||||
// inheritedfrom has value then we should disable it
|
||||
if (!isEmptyString(state.inheritedfrom)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(this.attprecisionRange(state));
|
||||
},
|
||||
},{
|
||||
id: 'collspcname', label: gettext('Collation'), cell: 'select',
|
||||
type: 'select', group: gettext('Definition'),
|
||||
deps: ['cltype'], options: this.collspcnameOptions,
|
||||
disabled: (state)=>{
|
||||
for(let o of this.datatypes) {
|
||||
if ( state.cltype == o.value ) {
|
||||
if(o.is_collatable) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, depChange: (state)=>{
|
||||
for(let o of this.datatypes) {
|
||||
if ( state.cltype == o.value ) {
|
||||
if(o.is_collatable) return {};
|
||||
}
|
||||
}
|
||||
return {collspcname: null};
|
||||
}
|
||||
},{
|
||||
id: 'attstattarget', label: gettext('Statistics'), cell: 'text',
|
||||
type: 'text', disabled: obj.inSchemaWithColumnCheck, mode: ['properties', 'edit'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'attstorage', label: gettext('Storage'), group: gettext('Definition'),
|
||||
type: 'select', mode: ['properties', 'edit'],
|
||||
cell: 'select', disabled: obj.inSchemaWithColumnCheck, first_empty: true,
|
||||
controlProps: { placeholder: gettext('Select storage'),
|
||||
allowClear: false,
|
||||
},
|
||||
options: [
|
||||
{label: 'PLAIN', value: 'p'},
|
||||
{label: 'MAIN', value: 'm'},
|
||||
{label: 'EXTERNAL', value: 'e'},
|
||||
{label: 'EXTENDED', value: 'x'},
|
||||
],
|
||||
},{
|
||||
id: 'defval', label: gettext('Default'), cell: 'text',
|
||||
type: 'text', group: gettext('Constraints'), deps: ['cltype', 'colconstype'],
|
||||
disabled: function(state) {
|
||||
var isDisabled = false;
|
||||
if(!obj.inSchemaWithModelCheck(state)) {
|
||||
isDisabled = ['serial', 'bigserial', 'smallserial'].indexOf(state.cltype) > -1;
|
||||
}
|
||||
isDisabled = isDisabled || state.colconstype != 'n';
|
||||
return isDisabled;
|
||||
}, depChange: (state)=>{
|
||||
var isDisabled = false;
|
||||
if(!obj.inSchemaWithModelCheck(state)) {
|
||||
isDisabled = ['serial', 'bigserial', 'smallserial'].indexOf(state.cltype) > -1;
|
||||
}
|
||||
isDisabled = isDisabled || state.colconstype != 'n';
|
||||
if (isDisabled && obj.isNew(state)) {
|
||||
return {defval: undefined};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
|
||||
type: 'switch', minWidth: 80,
|
||||
group: gettext('Constraints'), editable: this.editableCheckForTable,
|
||||
deps: ['colconstype'],
|
||||
disabled: (state) => {
|
||||
return obj.inSchemaWithColumnCheck(state);
|
||||
}, depChange:(state)=>{
|
||||
if (state.colconstype == 'i') {
|
||||
return {attnotnull: true};
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'colconstype',
|
||||
label: gettext('Type'),
|
||||
cell: 'text',
|
||||
group: gettext('Constraints'),
|
||||
type: (state)=>{
|
||||
var options = [
|
||||
{'label': gettext('NONE'), 'value': 'n'},
|
||||
{'label': gettext('IDENTITY'), 'value': 'i'},
|
||||
];
|
||||
|
||||
if (this.nodeInfo && this.nodeInfo.server &&
|
||||
this.nodeInfo.server.version >= 120000) {
|
||||
// You can't change the existing column to Generated column.
|
||||
if (this.isNew(state)) {
|
||||
options.push({
|
||||
'label': gettext('GENERATED'),
|
||||
'value': 'g',
|
||||
});
|
||||
} else {
|
||||
options.push({
|
||||
'label': gettext('GENERATED'),
|
||||
'value': 'g',
|
||||
'disabled': true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'toggle',
|
||||
options: options,
|
||||
};
|
||||
},
|
||||
disabled: function(state) {
|
||||
if (!this.isNew(state) && state.colconstype == 'g') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, min_version: 100000,
|
||||
}, {
|
||||
id: 'attidentity', label: gettext('Identity'),
|
||||
cell: 'select', type: 'select',
|
||||
controlProps: {placeholder: gettext('Select identity'), allowClear: false},
|
||||
min_version: 100000, group: gettext('Constraints'),
|
||||
options: [
|
||||
{label: gettext('ALWAYS'), value: 'a'},
|
||||
{label: gettext('BY DEFAULT'), value: 'd'},
|
||||
],
|
||||
deps: ['colconstype'],
|
||||
visible: this.isTypeIdentity,
|
||||
disabled: false,
|
||||
},{
|
||||
id: 'seqincrement', label: gettext('Increment'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: this.isIdentityColumn,
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'seqstart', label: gettext('Start'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
disabled: this.isIdentityColumn, deps: ['attidentity', 'colconstype'],
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'seqmin', label: gettext('Minimum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: this.isIdentityColumn,
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'seqmax', label: gettext('Maximum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: this.isIdentityColumn,
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'seqcache', label: gettext('Cache'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: this.isIdentityColumn,
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'seqcycle', label: gettext('Cycled'), type: 'switch',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: this.isIdentityColumn,
|
||||
visible: this.isTypeIdentity,
|
||||
},{
|
||||
id: 'genexpr', label: gettext('Expression'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min_version: 120000, deps: ['colconstype'], visible: this.isTypeGenerated,
|
||||
readonly: function(state) {
|
||||
return !this.isNew(state);
|
||||
},
|
||||
},{
|
||||
id: 'is_pk', label: gettext('Primary key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'is_fk', label: gettext('Foreign key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'is_inherited', label: gettext('Inherited?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('Definition'),
|
||||
},{
|
||||
id: 'tbls_inherited', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', mode: ['properties'], deps: ['is_inherited'],
|
||||
group: gettext('Definition'),
|
||||
visible: function(state) {
|
||||
return !_.isUndefined(state.is_inherited) && state.is_inherited;
|
||||
},
|
||||
},{
|
||||
id: 'is_sys_column', label: gettext('System column?'), cell: 'text',
|
||||
type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'text',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'attoptions', label: gettext('Variables'), type: 'collection',
|
||||
group: gettext('Variables'),
|
||||
schema: new VariableSchema([], null, null, ['name', 'value']),
|
||||
uniqueCol : ['name'], mode: ['edit', 'create'],
|
||||
canAdd: true, canEdit: false, canDelete: true,
|
||||
}, {
|
||||
id: 'attacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: gettext('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'),
|
||||
mode: ['edit', 'create'], editable: false, type: 'collection',
|
||||
min_version: 90100, canEdit: false, canDelete: true,
|
||||
uniqueCol : ['provider'],
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
var msg = undefined;
|
||||
|
||||
if (!_.isUndefined(state.cltype) && !isEmptyString(state.attlen)) {
|
||||
// Validation for Length field
|
||||
if (state.attlen < state.min_val_attlen)
|
||||
msg = gettext('Length/Precision should not be less than: ') + state.min_val_attlen;
|
||||
if (state.attlen > state.max_val_attlen)
|
||||
msg = gettext('Length/Precision should not be greater than: ') + state.max_val_attlen;
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
setError('attlen', msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isUndefined(state.cltype) && !isEmptyString(state.attprecision)) {
|
||||
// Validation for precision field
|
||||
if (state.attprecision < state.min_val_attprecision)
|
||||
msg = gettext('Scale should not be less than: ') + state.min_val_attprecision;
|
||||
if (state.attprecision > state.max_val_attprecision)
|
||||
msg = gettext('Scale should not be greater than: ') + state.max_val_attprecision;
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
setError('attprecision', msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.colconstype == 'g' && isEmptyString(state.genexpr)) {
|
||||
msg = gettext('Expression value cannot be empty.');
|
||||
setError('genexpr', msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.isNew(state) && state.colconstype == 'i'
|
||||
&& (this.origData.attidentity == 'a' || this.origData.attidentity == 'd')
|
||||
&& (state.attidentity == 'a' || state.attidentity == 'd')) {
|
||||
if(isEmptyString(state.seqincrement)) {
|
||||
msg = gettext('Increment value cannot be empty.');
|
||||
setError('seqincrement', msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(state.seqmin)) {
|
||||
msg = gettext('Minimum value cannot be empty.');
|
||||
setError('seqmin', msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(state.seqmax)) {
|
||||
msg = gettext('Maximum value cannot be empty.');
|
||||
setError('seqmax', msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(state.seqcache)) {
|
||||
msg = gettext('Cache value cannot be empty.');
|
||||
setError('seqcache', msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEmptyString(state.seqmin) || isEmptyString(state.seqmax))
|
||||
return false;
|
||||
|
||||
if ((state.seqmin == 0 && state.seqmax == 0) ||
|
||||
(parseInt(state.seqmin, 10) >= parseInt(state.seqmax, 10))) {
|
||||
setError('seqmin', gettext('Minimum value must be less than maximum value.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.seqstart && state.seqmin && parseInt(state.seqstart) < parseInt(state.seqmin)) {
|
||||
setError('seqstart', gettext('Start value cannot be less than minimum value.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.seqstart && state.seqmax && parseInt(state.seqstart) > parseInt(state.seqmax)) {
|
||||
setError('seqstart', gettext('Start value cannot be greater than maximum value.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import CheckConstraintSchema from './check_constraint.ui';
|
||||
|
||||
// Check Constraint Module: Node
|
||||
define('pgadmin.node.check_constraint', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
@ -90,108 +92,9 @@ define('pgadmin.node.check_constraint', [
|
||||
},
|
||||
},
|
||||
canDrop: schemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
description: undefined,
|
||||
consrc: undefined,
|
||||
connoinherit: undefined,
|
||||
convalidated: true,
|
||||
},
|
||||
// Check Constraint Schema
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type:'text', cell:'string',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cellHeaderClasses:'width_percent_40',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System check constraint?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), type: 'multiline', cell:
|
||||
'string', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(m) {
|
||||
var name = m.get('name');
|
||||
if (!(name && name != '')) {
|
||||
setTimeout(function(){
|
||||
if(m.get('comment') && m.get('comment') !== '')
|
||||
m.set('comment', null);
|
||||
},10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'consrc', label: gettext('Check'), type: 'multiline', cell:
|
||||
'string', group: gettext('Definition'), mode: ['properties', 'create', 'edit'],
|
||||
readonly: 'isReadonly', editable: false,
|
||||
},{
|
||||
id: 'connoinherit', label: gettext('No inherit?'), type:
|
||||
'switch', cell: 'boolean', group: gettext('Definition'), mode:
|
||||
['properties', 'create', 'edit'], min_version: 90200,
|
||||
disabled: function(m) {
|
||||
// Disabled if table is a partitioned table.
|
||||
if ((_.has(m , 'top') && !_.isUndefined(m.top) && m.top.get('is_partitioned')) ||
|
||||
(_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
|
||||
_.has(m.node_info.table, 'is_partitioned') && m.node_info.table.is_partitioned)
|
||||
){
|
||||
setTimeout(function(){
|
||||
m.set('connoinherit', false);
|
||||
},10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
readonly: 'isReadonly',
|
||||
},{
|
||||
id: 'convalidated', label: gettext('Don\'t validate?'), type: 'switch', cell:
|
||||
'boolean', group: gettext('Definition'), min_version: 90200,
|
||||
disabled: function(m) {
|
||||
if ((_.isFunction(m.isNew) && !m.isNew()) ||
|
||||
(_.has(m, 'handler') &&
|
||||
!_.isUndefined(m.handler) &&
|
||||
!_.isUndefined(m.get('oid')))) {
|
||||
|
||||
return !m.get('convalidated');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
// Client Side Validation
|
||||
validate: function() {
|
||||
var err = {},
|
||||
errmsg;
|
||||
|
||||
if (_.isUndefined(this.get('consrc')) || String(this.get('consrc')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['consrc'] = gettext('Check cannot be empty.');
|
||||
errmsg = err['consrc'];
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
if (_.size(err)) {
|
||||
this.trigger('on-status', {msg: errmsg});
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
},
|
||||
isReadonly: function(m) {
|
||||
return ((_.has(m, 'handler') &&
|
||||
!_.isUndefined(m.handler) &&
|
||||
!_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew()));
|
||||
},
|
||||
}),
|
||||
getSchema: function(){
|
||||
return new CheckConstraintSchema();
|
||||
},
|
||||
// Below function will enable right click menu for creating check constraint.
|
||||
canCreate: function(itemData, item, data) {
|
||||
// If check is false then , we will allow create menu
|
||||
|
@ -0,0 +1,103 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
export default class CheckConstraintSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
description: undefined,
|
||||
consrc: undefined,
|
||||
connoinherit: undefined,
|
||||
convalidated: true,
|
||||
});
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get inTable() {
|
||||
if(_.isUndefined(this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
return !_.isUndefined(this.nodeInfo['table']);
|
||||
}
|
||||
|
||||
isReadonly(state) {
|
||||
// If we are in table edit mode then
|
||||
if(this.top) {
|
||||
return !_.isUndefined(state.oid);
|
||||
}
|
||||
return !this.isNew(state);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type:'text', cell:'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'text',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System check constraint?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), type: 'multiline', cell: 'text',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled: function(state) {
|
||||
if(isEmptyString(state.name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(isEmptyString(state.name)) {
|
||||
return {comment: ''};
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'consrc', label: gettext('Check'), type: 'multiline', cell: 'text',
|
||||
group: gettext('Definition'), mode: ['properties', 'create', 'edit'],
|
||||
readonly: obj.isReadonly, editable: false, noEmpty: true,
|
||||
},{
|
||||
id: 'connoinherit', label: gettext('No inherit?'), type: 'switch', cell: 'switch',
|
||||
group: gettext('Definition'), mode: ['properties', 'create', 'edit'], min_version: 90200,
|
||||
deps: [['is_partitioned']],
|
||||
disabled: function() {
|
||||
// Disabled if table is a partitioned table.
|
||||
if(obj.inTable && obj.top?.sessData.is_partitioned) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
depChange: ()=>{
|
||||
if(obj.inTable && obj.top?.sessData.is_partitioned) {
|
||||
return {connoinherit: false};
|
||||
}
|
||||
},
|
||||
readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'convalidated', label: gettext('Don\'t validate?'), type: 'switch', cell: 'switch',
|
||||
group: gettext('Definition'), min_version: 90200,
|
||||
readonly: (state)=>{
|
||||
// If we are in table edit mode then
|
||||
if(obj.inTable && obj.top && !obj.top.isNew()) {
|
||||
return !(_.isUndefined(state.oid) || state.convalidated);
|
||||
}
|
||||
if(!obj.isNew(state) && !obj.origData.convalidated) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}];
|
||||
}
|
||||
|
||||
validate() {
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,449 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../../../static/js/SchemaView';
|
||||
import DataGridViewWithHeaderForm from '../../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm';
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
|
||||
import { pgAlertify } from '../../../../../../../../../../static/js/helpers/legacyConnector';
|
||||
|
||||
export function getNodeExclusionConstraintSchema(treeNodeInfo, itemNodeData, pgBrowser, noColumns=false) {
|
||||
let tableNode = pgBrowser.Nodes['table'];
|
||||
return new ExclusionConstraintSchema({
|
||||
columns: noColumns ? [] : ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, {includeItemKeys: ['datatype']}),
|
||||
amname: ()=>getNodeAjaxOptions('get_access_methods', tableNode, treeNodeInfo, itemNodeData),
|
||||
spcname: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
|
||||
return (m.label != 'pg_global');
|
||||
}),
|
||||
getOperClass: (urlParams)=>getNodeAjaxOptions('get_oper_class', tableNode, treeNodeInfo, itemNodeData, {urlParams: urlParams}, (data)=>{
|
||||
let res = [];
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d[0], value: d[1]});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}),
|
||||
getOperator: (urlParams)=>getNodeAjaxOptions('get_operator', tableNode, treeNodeInfo, itemNodeData, {urlParams: urlParams}, (data)=>{
|
||||
let res = [];
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d[0], value: d[1]});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
class ExclusionColHeaderSchema extends BaseUISchema {
|
||||
constructor(columns) {
|
||||
super({
|
||||
is_exp: undefined,
|
||||
column: undefined,
|
||||
expression: undefined,
|
||||
});
|
||||
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
addDisabled(state) {
|
||||
return !(state.is_exp ? state.expression : state.column);
|
||||
}
|
||||
|
||||
/* Data to ExclusionColumnSchema will added using the header form */
|
||||
getNewData(data) {
|
||||
let colType = data.is_exp ? null : _.find(this.columnOptions, (col)=>col.value==data.column)?.datatype;
|
||||
return this.exColumnSchema.getNewData({
|
||||
is_exp: data.is_exp,
|
||||
column: data.is_exp ? data.expression : data.column,
|
||||
col_type: colType,
|
||||
});
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'is_exp', label: gettext('Is expression'), type:'switch', editable: false,
|
||||
},{
|
||||
id: 'column', label: gettext('Column'), type: 'select', editable: false,
|
||||
options: this.columns, deps: ['is_exp'],
|
||||
optionsReloadBasis: this.columns?.map ? _.join(this.columns.map((c)=>c.label), ',') : null,
|
||||
optionsLoaded: (res)=>this.columnOptions=res,
|
||||
disabled: (state)=>state.is_exp,
|
||||
},{
|
||||
id: 'expression', label: gettext('Expression'), editable: false, deps: ['is_exp'],
|
||||
type: 'text', disabled: (state)=>!state.is_exp,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
class ExclusionColumnSchema extends BaseUISchema {
|
||||
constructor(getOperator) {
|
||||
super({
|
||||
column: undefined,
|
||||
is_exp: false,
|
||||
oper_class: undefined,
|
||||
order: false,
|
||||
nulls_order: false,
|
||||
operator:undefined,
|
||||
col_type:undefined,
|
||||
is_sort_nulls_applicable: false,
|
||||
});
|
||||
|
||||
this.operClassOptions = [];
|
||||
this.operatorOptions = [];
|
||||
this.getOperator = getOperator;
|
||||
this.isNewExCons = true;
|
||||
this.amname = null;
|
||||
}
|
||||
|
||||
isEditable() {
|
||||
if(!this.isNewExCons) {
|
||||
return false;
|
||||
} else if(this.amname === 'btree') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setOperClassOptions(options) {
|
||||
this.operClassOptions = options;
|
||||
}
|
||||
|
||||
changeDefaults(data) {
|
||||
this.defaultColVals = data;
|
||||
}
|
||||
|
||||
getNewData(data) {
|
||||
return {
|
||||
...super.getNewData(data),
|
||||
...this.defaultColVals,
|
||||
};
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'is_exp', label: '', type:'', editable: false, minWidth: 20,
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: function (rawValue) {
|
||||
return rawValue ? 'E' : 'C';
|
||||
},
|
||||
}
|
||||
}, visible: false,
|
||||
},{
|
||||
id: 'column', label: gettext('Col/Exp'), type:'', editable: false,
|
||||
cell:'', minWidth: 125,
|
||||
},{
|
||||
id: 'oper_class', label: gettext('Operator class'), cell:'select',
|
||||
options: this.operClassOptions,
|
||||
minWidth: 185,
|
||||
editable: obj.isEditable,
|
||||
controlProps: {
|
||||
allowClear: true, placeholder: gettext('Select the operator class'),
|
||||
},
|
||||
},{
|
||||
id: 'order', label: gettext('Order'), type: 'select', cell: 'select',
|
||||
options: [
|
||||
{label: 'ASC', value: true},
|
||||
{label: 'DESC', value: false},
|
||||
],
|
||||
editable: obj.isEditable, minWidth: 110,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
},{
|
||||
id: 'nulls_order', label: gettext('NULLs order'), type:'select', cell: 'select',
|
||||
options: [
|
||||
{label: 'FIRST', value: true},
|
||||
{label: 'LAST', value: false},
|
||||
], controlProps: {allowClear: false},
|
||||
editable: obj.isEditable, minWidth: 110,
|
||||
},{
|
||||
id: 'operator', label: gettext('Operator'), type: 'select',
|
||||
minWidth: 95,
|
||||
editable: function() {
|
||||
return obj.isNewExCons;
|
||||
},
|
||||
cell: (state)=>{
|
||||
return {
|
||||
cell: 'select',
|
||||
options: ()=>obj.getOperator({col_type: state.col_type}),
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export default class ExclusionConstraintSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo, getColumns) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
spcname: undefined,
|
||||
amname: 'gist',
|
||||
fillfactor: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
columns: [],
|
||||
include: [],
|
||||
});
|
||||
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.exHeaderSchema = new ExclusionColHeaderSchema(fieldOptions.columns, getColumns);
|
||||
this.exColumnSchema = new ExclusionColumnSchema(fieldOptions.getOperator);
|
||||
this.exHeaderSchema.exColumnSchema = this.exColumnSchema;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get inTable() {
|
||||
if(_.isUndefined(this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
return !_.isUndefined(this.nodeInfo['table']);
|
||||
}
|
||||
|
||||
initialise(data) {
|
||||
this.exColumnSchema.isNewExCons = this.isNew(data);
|
||||
this.amname = data.amname;
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.exHeaderSchema.changeColumnOptions(columns);
|
||||
this.fieldOptions.columns = columns;
|
||||
}
|
||||
|
||||
isReadonly(state) {
|
||||
// If we are in table edit mode then
|
||||
if(this.top) {
|
||||
return !_.isUndefined(state.oid);
|
||||
}
|
||||
return !this.isNew(state);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text', cell: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System foreign key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'text',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(state) {
|
||||
if(isEmptyString(state.name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, depChange: (state)=>{
|
||||
if(isEmptyString(state.name)) {
|
||||
return {comment: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'select', group: gettext('Definition'),
|
||||
controlProps: {allowClear:false}, options: this.fieldOptions.spcname,
|
||||
},{
|
||||
id: 'amname', label: gettext('Access method'),
|
||||
type: 'select', group: gettext('Definition'),
|
||||
options: this.fieldOptions.amname,
|
||||
deferredDepChange: (state, source, topState, actionObj)=>{
|
||||
return new Promise((resolve)=>{
|
||||
pgAlertify().confirm(
|
||||
gettext('Change access method?'),
|
||||
gettext('Changing access method will clear columns collection'),
|
||||
function () {
|
||||
if(state.amname == 'btree' || isEmptyString(state.amname)) {
|
||||
let indextype = 'btree';
|
||||
obj.exColumnSchema.setOperClassOptions(obj.fieldOptions.getOperClass({indextype:indextype}));
|
||||
obj.exColumnSchema.changeDefaults({
|
||||
order: true,
|
||||
nulls_order: true,
|
||||
is_sort_nulls_applicable: true,
|
||||
});
|
||||
} else {
|
||||
obj.exColumnSchema.setOperClassOptions([]);
|
||||
obj.exColumnSchema.changeDefaults({
|
||||
order: false,
|
||||
nulls_order: false,
|
||||
is_sort_nulls_applicable: false,
|
||||
});
|
||||
}
|
||||
obj.exColumnSchema.amname = state.amname;
|
||||
resolve(()=>({
|
||||
columns: [],
|
||||
}));
|
||||
},
|
||||
function() {
|
||||
resolve(()=>{
|
||||
return {
|
||||
amname: actionObj.oldState.amname,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
controlProps: {allowClear:true},
|
||||
readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'),
|
||||
type: 'int', group: gettext('Definition'), allowNull: true,
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
disabled: function(state) {
|
||||
// Disable if condeferred is false or unselected.
|
||||
if(state.condeferrable) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
readonly: obj.isReadonly,
|
||||
depChange: (state)=>{
|
||||
if(!state.condeferrable) {
|
||||
return {condeferred: false};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'indconstraint', label: gettext('Constraint'), cell: 'text',
|
||||
type: 'multiline', mode: ['create', 'edit', 'properties'], editable: false,
|
||||
group: gettext('Definition'), readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns/Expressions'),
|
||||
group: gettext('Columns'), type: 'collection',
|
||||
mode: ['create', 'edit'],
|
||||
editable: false, schema: this.exColumnSchema,
|
||||
headerSchema: this.exHeaderSchema, headerVisible: (state)=>obj.isNew(state),
|
||||
CustomControl: DataGridViewWithHeaderForm,
|
||||
uniqueCol: ['column'],
|
||||
canAdd: false, canDelete: function(state) {
|
||||
// We can't update columns of existing foreign key.
|
||||
return obj.isNew(state);
|
||||
},
|
||||
readonly: obj.isReadonly, cell: ()=>({
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (rawValue)=>{
|
||||
var cols = [],
|
||||
remoteCols = [];
|
||||
if (rawValue?.length > 0) {
|
||||
rawValue.forEach((col)=>{
|
||||
cols.push(col.local_column);
|
||||
remoteCols.push(col.referenced);
|
||||
});
|
||||
return '('+cols.join(', ')+') -> ('+ remoteCols.join(', ')+')';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
}
|
||||
},
|
||||
minWidth: 245,
|
||||
}),
|
||||
deps: ()=>{
|
||||
let ret = [];
|
||||
if(obj.inTable) {
|
||||
ret.push(['columns']);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
/* If in table, sync up value with columns in table */
|
||||
if(obj.inTable && !state) {
|
||||
/* the FK is removed by some other dep, this can be a no-op */
|
||||
return;
|
||||
}
|
||||
let currColumns = state.columns || [];
|
||||
if(obj.inTable && source[0] == 'columns') {
|
||||
if(actionObj.type == SCHEMA_STATE_ACTIONS.DELETE_ROW) {
|
||||
let oldColumn = _.get(actionObj.oldState, actionObj.path.concat(actionObj.value));
|
||||
currColumns = _.filter(currColumns, (cc)=>cc.local_column != oldColumn.name);
|
||||
} else if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE) {
|
||||
let tabColPath = _.slice(actionObj.path, 0, -1);
|
||||
let oldColName = _.get(actionObj.oldState, tabColPath).name;
|
||||
let idx = _.findIndex(currColumns, (cc)=>cc.local_column == oldColName);
|
||||
if(idx > -1) {
|
||||
currColumns[idx].local_column = _.get(topState, tabColPath).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {columns: currColumns};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'include', label: gettext('Include columns'), group: gettext('Columns'),
|
||||
type: ()=>({
|
||||
type: 'select',
|
||||
options: this.fieldOptions.columns,
|
||||
optionsReloadBasis: this.fieldOptions.columns?.map ? _.join(this.fieldOptions.columns.map((c)=>c.label), ',') : null,
|
||||
controlProps: {
|
||||
multiple: true,
|
||||
},
|
||||
}),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true,
|
||||
mode: ['properties', 'create', 'edit'], min_version: 110000,
|
||||
deps: ['index'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {include: []};
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
if ((_.isUndefined(state.columns) || _.isNull(state.columns) || state.columns.length < 1)) {
|
||||
setError('columns', gettext('Please specify columns for Foreign key.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNew(state)){
|
||||
if (state.autoindex && isEmptyString(state.coveringindex)) {
|
||||
setError('coveringindex', gettext('Please specify covering index name.'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,405 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../../../static/js/SchemaView';
|
||||
import DataGridViewWithHeaderForm from '../../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm';
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
|
||||
|
||||
export function getNodeForeignKeySchema(treeNodeInfo, itemNodeData, pgBrowser, noColumns=false) {
|
||||
return new ForeignKeySchema({
|
||||
local_column: noColumns ? [] : ()=>getNodeListByName('column', treeNodeInfo, itemNodeData),
|
||||
references: ()=>getNodeAjaxOptions('all_tables', pgBrowser.Nodes['table'], treeNodeInfo, itemNodeData, {cacheLevel: 'server'}, (rows)=>{
|
||||
return rows.map((r)=>({
|
||||
'value': r.value,
|
||||
'image': 'icon-table',
|
||||
'label': r.label,
|
||||
oid: r.oid,
|
||||
}));
|
||||
}),
|
||||
},
|
||||
treeNodeInfo,
|
||||
(params)=>{
|
||||
return getNodeAjaxOptions('get_columns', pgBrowser.Nodes['table'], treeNodeInfo, itemNodeData, {urlParams: params, useCache:false}, (rows)=>{
|
||||
return rows.map((r)=>({
|
||||
'value': r.name,
|
||||
'image': 'icon-column',
|
||||
'label': r.name,
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ForeignKeyHeaderSchema extends BaseUISchema {
|
||||
constructor(fieldOptions, getColumns) {
|
||||
super({
|
||||
local_column: undefined,
|
||||
references: undefined,
|
||||
referenced: undefined,
|
||||
});
|
||||
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.getColumns = getColumns;
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.fieldOptions.local_column = columns;
|
||||
}
|
||||
|
||||
addDisabled(state) {
|
||||
return !(state.local_column && state.references && state.referenced);
|
||||
}
|
||||
|
||||
/* Data to ForeignKeyColumnSchema will added using the header form */
|
||||
getNewData(data) {
|
||||
let references_table_name = _.find(this.refTables, (t)=>t.value==data.references)?.label;
|
||||
return {
|
||||
local_column: data.local_column,
|
||||
referenced: data.referenced,
|
||||
references: data.references,
|
||||
references_table_name: references_table_name,
|
||||
};
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'local_column', label: gettext('Local column'), type:'select', editable: false,
|
||||
options: this.fieldOptions.local_column,
|
||||
optionsReloadBasis: this.fieldOptions.local_column?.map ? _.join(this.fieldOptions.local_column.map((c)=>c.label), ',') : null,
|
||||
},{
|
||||
id: 'references', label: gettext('References'), type: 'select', editable: false,
|
||||
options: this.fieldOptions.references,
|
||||
optionsReloadBasis: this.fieldOptions.references?.map ? _.join(this.fieldOptions.references.map((c)=>c.label), ',') : null,
|
||||
optionsLoaded: (rows)=>obj.refTables=rows,
|
||||
},{
|
||||
id: 'referenced', label: gettext('Referencing'), editable: false, deps: ['references'],
|
||||
type: (state)=>{
|
||||
return {
|
||||
type: 'select',
|
||||
options: state.references ? ()=>this.getColumns({tid: state.references}) : [],
|
||||
optionsReloadBasis: state.references,
|
||||
};
|
||||
},
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
class ForeignKeyColumnSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super({
|
||||
local_column: undefined,
|
||||
referenced: undefined,
|
||||
references: undefined,
|
||||
references_table_name: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'local_column', label: gettext('Local'), type:'text', editable: false,
|
||||
cell:'', minWidth: 145,
|
||||
},{
|
||||
id: 'referenced', label: gettext('Referenced'), type: 'text', editable: false,
|
||||
cell:'', minWidth: 145,
|
||||
},{
|
||||
id: 'references_table_name', label: gettext('Referenced Table'), type: 'text', editable: false,
|
||||
cell:'', minWidth: 145,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export default class ForeignKeySchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo, getColumns) {
|
||||
super({
|
||||
name: undefined,
|
||||
reftab: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
confmatchtype: false,
|
||||
convalidated: undefined,
|
||||
columns: undefined,
|
||||
confupdtype: 'a',
|
||||
confdeltype: 'a',
|
||||
autoindex: true,
|
||||
coveringindex: undefined,
|
||||
hasindex:undefined,
|
||||
});
|
||||
|
||||
this.nodeInfo = nodeInfo;
|
||||
|
||||
this.fkHeaderSchema = new ForeignKeyHeaderSchema(fieldOptions, getColumns);
|
||||
this.fkHeaderSchema.fkObj = this;
|
||||
this.fkColumnSchema = new ForeignKeyColumnSchema();
|
||||
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get inTable() {
|
||||
if(_.isUndefined(this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
return !_.isUndefined(this.nodeInfo['table']);
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.fkHeaderSchema.changeColumnOptions(columns);
|
||||
}
|
||||
|
||||
isReadonly(state) {
|
||||
// If we are in table edit mode then
|
||||
if(this.top) {
|
||||
return !_.isUndefined(state.oid);
|
||||
}
|
||||
return !this.isNew(state);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text', cell: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System foreign key?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'text',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(state) {
|
||||
if(isEmptyString(state.name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, depChange: (state)=>{
|
||||
if(isEmptyString(state.name)) {
|
||||
return {comment: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
disabled: function(state) {
|
||||
// Disable if condeferred is false or unselected.
|
||||
if(state.condeferrable) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
readonly: obj.isReadonly,
|
||||
depChange: (state)=>{
|
||||
if(!state.condeferrable) {
|
||||
return {condeferred: false};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'confmatchtype', label: gettext('Match type'),
|
||||
type: 'toggle', group: gettext('Definition'),
|
||||
options: [
|
||||
{label: 'FULL', value: true},
|
||||
{label: 'SIMPLE', value: false},
|
||||
], readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'convalidated', label: gettext('Validated?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
readonly: (state)=>{
|
||||
// If we are in table edit mode then
|
||||
if(obj.inTable && obj.top && !obj.top.isNew()) {
|
||||
return !(_.isUndefined(state.oid) || !state.convalidated);
|
||||
}
|
||||
if(!obj.isNew(state) && obj.origData.convalidated) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'autoindex', label: gettext('Auto FK index?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['name', 'hasindex'],
|
||||
disabled: (state)=>{
|
||||
if(!obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
// If we are in table edit mode then
|
||||
if(obj.inTable) {
|
||||
// user is trying to add new constraint which should allowed for Unique
|
||||
if(obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(!obj.isNew(state) && state.autoindex && !isEmptyString(state.coveringindex)
|
||||
&& state.hasindex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(state.hasindex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
if(!obj.isNew(state)) {
|
||||
return {};
|
||||
}
|
||||
// If we are in table edit mode then
|
||||
if(obj.inTable) {
|
||||
// user is trying to add new constraint which should allowed for Unique
|
||||
if(obj.isNew(state)) {
|
||||
return {autoindex: false, coveringindex: ''};
|
||||
}
|
||||
}
|
||||
|
||||
let oldindex = 'fki_'+actionObj.oldState.name;
|
||||
if(state.hasindex) {
|
||||
return {};
|
||||
} else if(!state.autoindex) {
|
||||
return {coveringindex: ''};
|
||||
} else if(state.autoindex && !isEmptyString(state.name) &&
|
||||
(isEmptyString(state.coveringindex) || oldindex == state.coveringindex)){
|
||||
return {coveringindex: 'fki_'+state.name};
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'coveringindex', label: gettext('Covering index'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Definition'),
|
||||
deps:['autoindex', 'hasindex'],
|
||||
disabled: (state)=>{
|
||||
if(!obj.isNew(state) && state.autoindex && !isEmptyString(state.coveringindex)) {
|
||||
return true;
|
||||
}
|
||||
if(state.hasindex) {
|
||||
return true;
|
||||
} else if(!state.autoindex) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
readonly: this.isReadonly,
|
||||
},{
|
||||
id: 'references_table_name', label: gettext('Referenced Table'),
|
||||
type: 'text', group: gettext('Columns'), editable: false, visible:false,
|
||||
cell: '', deps: ['columns'],
|
||||
depChange: (state)=>{
|
||||
if(state.columns?.length > 0) {
|
||||
return {references_table_name: _.join(_.map(state.columns, 'references_table_name'), ',')};
|
||||
}
|
||||
return {references_table_name: undefined};
|
||||
}
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
group: gettext('Columns'), type: 'collection',
|
||||
mode: ['create', 'edit'],
|
||||
editable: false, schema: this.fkColumnSchema,
|
||||
headerSchema: this.fkHeaderSchema, headerVisible: (state)=>obj.isNew(state),
|
||||
CustomControl: DataGridViewWithHeaderForm,
|
||||
uniqueCol: ['local_column', 'references', 'referenced'],
|
||||
canAdd: false, canDelete: function(state) {
|
||||
// We can't update columns of existing foreign key.
|
||||
return obj.isNew(state);
|
||||
},
|
||||
readonly: obj.isReadonly, cell: ()=>({
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (rawValue)=>{
|
||||
var cols = [],
|
||||
remoteCols = [];
|
||||
if (rawValue?.length > 0) {
|
||||
rawValue.forEach((col)=>{
|
||||
cols.push(col.local_column);
|
||||
remoteCols.push(col.referenced);
|
||||
});
|
||||
return '('+cols.join(', ')+') -> ('+ remoteCols.join(', ')+')';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
}
|
||||
},
|
||||
minWidth: 245,
|
||||
}),
|
||||
deps: ()=>{
|
||||
let ret = [];
|
||||
if(obj.inTable) {
|
||||
ret.push(['columns']);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
/* If in table, sync up value with columns in table */
|
||||
if(obj.inTable && !state) {
|
||||
/* the FK is removed by some other dep, this can be a no-op */
|
||||
return;
|
||||
}
|
||||
let currColumns = state.columns || [];
|
||||
if(obj.inTable && source[0] == 'columns') {
|
||||
if(actionObj.type == SCHEMA_STATE_ACTIONS.DELETE_ROW) {
|
||||
let oldColumn = _.get(actionObj.oldState, actionObj.path.concat(actionObj.value));
|
||||
currColumns = _.filter(currColumns, (cc)=>cc.local_column != oldColumn.name);
|
||||
} else if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE) {
|
||||
let tabColPath = _.slice(actionObj.path, 0, -1);
|
||||
let oldColName = _.get(actionObj.oldState, tabColPath).name;
|
||||
let idx = _.findIndex(currColumns, (cc)=>cc.local_column == oldColName);
|
||||
if(idx > -1) {
|
||||
currColumns[idx].local_column = _.get(topState, tabColPath).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {columns: currColumns};
|
||||
},
|
||||
},{
|
||||
id: 'confupdtype', label: gettext('On update'),
|
||||
type:'select', group: gettext('Action'), mode: ['edit','create'],
|
||||
controlProps:{allowClear: false},
|
||||
options: [
|
||||
{label: 'NO ACTION', value: 'a'},
|
||||
{label: 'RESTRICT', value: 'r'},
|
||||
{label: 'CASCADE', value: 'c'},
|
||||
{label: 'SET NULL', value: 'n'},
|
||||
{label: 'SET DEFAULT', value: 'd'},
|
||||
], readonly: obj.isReadonly,
|
||||
},{
|
||||
id: 'confdeltype', label: gettext('On delete'),
|
||||
type:'select', group: gettext('Action'), mode: ['edit','create'],
|
||||
select2:{allowClear: false},
|
||||
options: [
|
||||
{label: 'NO ACTION', value: 'a'},
|
||||
{label: 'RESTRICT', value: 'r'},
|
||||
{label: 'CASCADE', value: 'c'},
|
||||
{label: 'SET NULL', value: 'n'},
|
||||
{label: 'SET DEFAULT', value: 'd'},
|
||||
], readonly: obj.isReadonly,
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
if ((_.isUndefined(state.columns) || _.isNull(state.columns) || state.columns.length < 1)) {
|
||||
setError('columns', gettext('Please specify columns for Foreign key.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNew(state)){
|
||||
if (state.autoindex && isEmptyString(state.coveringindex)) {
|
||||
setError('coveringindex', gettext('Please specify covering index name.'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -7,11 +7,13 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
|
||||
import PrimaryKeySchema from './primary_key.ui';
|
||||
|
||||
define('pgadmin.node.primary_key', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's node class for index constraint node
|
||||
if (!pgBrowser.Nodes['primary_key']) {
|
||||
@ -96,573 +98,15 @@ define('pgadmin.node.primary_key', [
|
||||
return !is_immediate_parent_table_partitioned;
|
||||
}
|
||||
},
|
||||
|
||||
// Define the model for index constraint node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
spcname: undefined,
|
||||
index: undefined,
|
||||
fillfactor: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
columns: [],
|
||||
include: [],
|
||||
},
|
||||
|
||||
// Define the schema for the index constraint node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cellHeaderClasses:'width_percent_40',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'], editable: false,
|
||||
cellHeaderClasses:'width_percent_20',
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System primary key?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(m) {
|
||||
var name = m.get('name');
|
||||
if (!(name && name != '')) {
|
||||
setTimeout(function(){
|
||||
if(m.get('comment') && m.get('comment') !== '') {
|
||||
m.set('comment', null);
|
||||
}
|
||||
},10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
type: 'collection', group: gettext('Definition'),
|
||||
editable: false,
|
||||
cell: Backgrid.StringCell.extend({
|
||||
initialize: function() {
|
||||
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
|
||||
|
||||
var self = this,
|
||||
collection = this.model.get('columns');
|
||||
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.removeColumn);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
collection.on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
self.listenTo(collection, 'add', self.render);
|
||||
self.listenTo(collection, 'remove', self.render);
|
||||
},
|
||||
removeColumn: function(m) {
|
||||
var self = this,
|
||||
removedCols = self.model.get('columns').where(
|
||||
{column: m.get('name')}
|
||||
);
|
||||
|
||||
self.model.get('columns').remove(removedCols);
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
|
||||
var key = 'primary_key';
|
||||
setTimeout(function () {
|
||||
var constraints = self.model.top.get(key),
|
||||
removed = [];
|
||||
constraints.each(function(constraint) {
|
||||
if (constraint.get('columns').length == 0) {
|
||||
removed.push(constraint);
|
||||
}
|
||||
});
|
||||
constraints.remove(removed);
|
||||
},100);
|
||||
|
||||
},
|
||||
resetColOptions : function(m) {
|
||||
var self = this,
|
||||
updatedCols = self.model.get('columns').where(
|
||||
{column: m.previous('name')}
|
||||
);
|
||||
if (updatedCols.length > 0) {
|
||||
/*
|
||||
* Table column name has changed so update
|
||||
* column name in primary key as well.
|
||||
*/
|
||||
updatedCols[0].set(
|
||||
{'column': m.get('name')},
|
||||
{silent: true});
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
},
|
||||
formatter: {
|
||||
fromRaw: function (rawValue) {
|
||||
return rawValue.pluck('column').toString();
|
||||
},
|
||||
toRaw: function (val) { return val; },
|
||||
},
|
||||
render: function() {
|
||||
return Backgrid.StringCell.prototype.render.apply(this, arguments);
|
||||
},
|
||||
remove: function() {
|
||||
var tableCols = this.model.top.get('columns'),
|
||||
primary_key_col = this.model.get('columns');
|
||||
|
||||
if (primary_key_col) {
|
||||
primary_key_col.off('pgadmin:multicolumn:updated');
|
||||
}
|
||||
|
||||
this.stopListening(tableCols, 'remove' , self.removeColumn);
|
||||
this.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
|
||||
Backgrid.StringCell.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
canDelete: true, canAdd: true,
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
width: 'style',
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
keyPathAccessor: function(obj, path) {
|
||||
var res = obj;
|
||||
if(_.isArray(res)) {
|
||||
return _.map(res, function(o) { return o['column'];
|
||||
});
|
||||
}
|
||||
path = path.split('.');
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
if (_.isNull(res)) return null;
|
||||
if (_.isEmpty(path[i])) continue;
|
||||
if (!_.isUndefined(res[path[i]])) res = res[path[i]];
|
||||
}
|
||||
return _.isObject(res) && !_.isArray(res) ? null : res;
|
||||
},
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
var self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
self.model.get('columns').on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
},
|
||||
resetColOptions: function() {
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.custom_options();
|
||||
self.render.apply(self);
|
||||
}, 50);
|
||||
},
|
||||
custom_options: function() {
|
||||
// We will add all the columns entered by user in table model
|
||||
var columns = this.model.top.get('columns'),
|
||||
added_columns_from_tables = [];
|
||||
|
||||
if (columns.length > 0) {
|
||||
_.each(columns.models, function(m) {
|
||||
var col = m.get('name');
|
||||
if(!_.isUndefined(col) && !_.isNull(col)) {
|
||||
added_columns_from_tables.push(
|
||||
{label: col, value: col, image:'icon-column'}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set the values in to options so that user can select
|
||||
this.field.set('options', added_columns_from_tables);
|
||||
},
|
||||
onChange: function() {
|
||||
var self = this,
|
||||
model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
vals = this.getValueFromDOM(),
|
||||
collection = model.get(name),
|
||||
removed = [];
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
|
||||
/*
|
||||
* Iterate through all the values, and find out how many are already
|
||||
* present in the collection.
|
||||
*/
|
||||
collection.each(function(m) {
|
||||
var column = m.get('column'),
|
||||
idx = _.indexOf(vals, column);
|
||||
|
||||
if (idx > -1) {
|
||||
vals.splice(idx, 1);
|
||||
} else {
|
||||
removed.push(column);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Adding new values
|
||||
*/
|
||||
|
||||
_.each(vals, function(v) {
|
||||
var m = new (self.field.get('model'))(
|
||||
{column: v}, { silent: true,
|
||||
top: self.model.top,
|
||||
collection: collection,
|
||||
handler: collection,
|
||||
});
|
||||
|
||||
collection.add(m);
|
||||
});
|
||||
|
||||
/*
|
||||
* Removing unwanted!
|
||||
*/
|
||||
_.each(removed, function(v) {
|
||||
collection.remove(collection.where({column: v}));
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
remove: function() {
|
||||
if(this.model.handler) {
|
||||
var self = this,
|
||||
tableCols = self.model.top.get('columns');
|
||||
self.stopListening(tableCols, 'remove' , self.resetColOptions);
|
||||
self.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
self.model.get('columns').off('pgadmin:multicolumn:updated');
|
||||
|
||||
Backform.Select2Control.prototype.remove.apply(this, arguments);
|
||||
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.remove.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var index = this.model.get('index');
|
||||
if(!_.isUndefined(index) && index != '') {
|
||||
var col = this.model.get('columns');
|
||||
col.reset([], {silent: true});
|
||||
}
|
||||
return Backform.Select2Control.prototype.render.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
column: undefined,
|
||||
},
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
transform : function(data){
|
||||
var res = [];
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d.label, value: d.label, image:'icon-column'});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
select2:{allowClear:false},
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should be allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update columns of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
return !(_.isUndefined(index) || index == '');
|
||||
},
|
||||
},{
|
||||
id: 'include', label: gettext('Include columns'),
|
||||
type: 'array', group: gettext('Definition'),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'],
|
||||
visible: function(m) {
|
||||
/* In table properties, m.node_info is not available */
|
||||
m = m.top;
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 110000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
allowClear: false,
|
||||
width: 'style',
|
||||
multiple: true,
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
var self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
resetColOptions: function() {
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.custom_options();
|
||||
self.render.apply(self);
|
||||
}, 50);
|
||||
},
|
||||
custom_options: function() {
|
||||
// We will add all the columns entered by user in table model
|
||||
var columns = this.model.top.get('columns'),
|
||||
added_columns_from_tables = [];
|
||||
|
||||
if (columns.length > 0) {
|
||||
_.each(columns.models, function(m) {
|
||||
var col = m.get('name');
|
||||
if(!_.isUndefined(col) && !_.isNull(col)) {
|
||||
added_columns_from_tables.push(
|
||||
{label: col, value: col, image:'icon-column'}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set the values in to options so that user can select
|
||||
this.field.set('options', added_columns_from_tables);
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should be allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update columns of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('include', []);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: 'node-list-by-name', node: 'tablespace',
|
||||
deps: ['index'],
|
||||
select2:{allowClear:false},
|
||||
filter: function(m) {
|
||||
// Don't show pg_global tablespace in selection.
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return new PrimaryKeySchema({
|
||||
columns: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData),
|
||||
spcname: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
|
||||
return (m.label != 'pg_global');
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
m = m.top || m;
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('spcname', '');
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'index', label: gettext('Index'),
|
||||
mode: ['create'],
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: Backform.NodeListByNameControl.extend({
|
||||
initialize:function() {
|
||||
Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
select2:{allowClear:true}, node: 'index',
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then disable it
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't update index of existing index constraint.
|
||||
return !m.isNew();
|
||||
},
|
||||
// We will not show this field in Create Table mode
|
||||
visible: function(m) {
|
||||
return !_.isUndefined(m.top.node_info['table']);
|
||||
},
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
|
||||
type: 'int', group: gettext('Definition'), allowNull: true,
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('fillfactor', null);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'), deps: ['index'],
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update condeferrable of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferrable'))
|
||||
m.set('condeferrable', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update condeferred of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if condeferred is false or unselected.
|
||||
if(m.get('condeferrable') == true) {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferred'))
|
||||
m.set('condeferred', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
this.errorModel.clear();
|
||||
// Clear parent's error as well
|
||||
if (_.has(this, 'top')) {
|
||||
this.top.errorModel.clear();
|
||||
}
|
||||
|
||||
var columns = this.get('columns'),
|
||||
index = this.get('index');
|
||||
|
||||
if ((_.isUndefined(index) || String(index).replace(/^\s+|\s+$/g, '') == '') &&
|
||||
(_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
|
||||
var msg = gettext('Please specify columns for %s.', gettext('Primary key'));
|
||||
this.errorModel.set('columns', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
index: ()=>getNodeListByName('index', treeNodeInfo, itemNodeData),
|
||||
}, treeNodeInfo);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,271 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../../../static/js/SchemaView';
|
||||
export default class PrimaryKeySchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
spcname: undefined,
|
||||
index: undefined,
|
||||
fillfactor: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
columns: [],
|
||||
include: [],
|
||||
});
|
||||
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.nodeInfo = nodeInfo;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get inTable() {
|
||||
if(_.isUndefined(this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
return _.isUndefined(this.nodeInfo['table']);
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.fieldOptions.columns = columns;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cell: 'text',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'text',
|
||||
type: 'text' , mode: ['properties'], editable: false,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System primary key?'),
|
||||
cell:'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'multiline',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled: (state)=>{
|
||||
if(isEmptyString(state.name)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, depChange: (state)=>{
|
||||
if(isEmptyString(state.name)){
|
||||
return {comment: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
deps: ()=>{
|
||||
let ret = ['index'];
|
||||
if(obj.inTable) {
|
||||
ret.push(['columns']);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
/* If in table, sync up value with columns in table */
|
||||
let currColumns = state.columns || [];
|
||||
if(obj.inTable && source[0] == 'columns') {
|
||||
if(actionObj.type == SCHEMA_STATE_ACTIONS.DELETE_ROW) {
|
||||
let oldColumn = _.get(actionObj.oldState, actionObj.path.concat(actionObj.value));
|
||||
currColumns = _.filter(currColumns, (cc)=>cc.column != oldColumn.name);
|
||||
} else if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE) {
|
||||
let tabColPath = _.slice(actionObj.path, 0, -1);
|
||||
let oldColName = _.get(actionObj.oldState, tabColPath).name;
|
||||
let idx = _.findIndex(currColumns, (cc)=>cc.column == oldColName);
|
||||
if(idx > -1) {
|
||||
currColumns[idx].column = _.get(topState, tabColPath).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {columns: currColumns};
|
||||
},
|
||||
cell: ()=>({
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=>{
|
||||
/* remove the column key and pass as array */
|
||||
return _.join((backendVal||[]).map((singleVal)=>singleVal.column));
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
type: ()=>({
|
||||
type: 'select',
|
||||
optionsReloadBasis: obj.fieldOptions.columns?.map ? _.join(obj.fieldOptions.columns.map((c)=>c.label), ',') : null,
|
||||
options: obj.fieldOptions.columns,
|
||||
controlProps: {
|
||||
allowClear:false,
|
||||
multiple: true,
|
||||
formatter: {
|
||||
fromRaw: (backendVal, allOptions)=>{
|
||||
/* remove the column key and pass as array */
|
||||
let optValues = (backendVal||[]).map((singleVal)=>singleVal.column);
|
||||
return _.filter(allOptions, (opt)=>optValues.indexOf(opt.value)>-1);
|
||||
},
|
||||
toRaw: (value)=>{
|
||||
/* take the array and convert to column key collection */
|
||||
return (value||[]).map((singleVal)=>({column: singleVal.value}));
|
||||
},
|
||||
},
|
||||
},
|
||||
}), group: gettext('Definition'),
|
||||
editable: false,
|
||||
readonly: function(state) {
|
||||
if(!obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
},{
|
||||
id: 'include', label: gettext('Include columns'),
|
||||
type: ()=>({
|
||||
type: 'select',
|
||||
options: this.fieldOptions.columns,
|
||||
controlProps: {
|
||||
multiple: true,
|
||||
},
|
||||
}), group: gettext('Definition'),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true,
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
visible: function() {
|
||||
/* In table properties, nodeInfo is not available */
|
||||
if(this.getServerVersion() >= 110000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
deps: ['index'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {include: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'select', group: gettext('Definition'),
|
||||
options: this.fieldOptions.spcname,
|
||||
deps: ['index'],
|
||||
controlProps:{allowClear:false},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {spcname: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'index', label: gettext('Index'),
|
||||
mode: ['create'],
|
||||
type: 'select', group: gettext('Definition'),
|
||||
controlProps:{
|
||||
allowClear:true,
|
||||
options: this.fieldOptions.index,
|
||||
},
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
// We will not show this field in Create Table mode
|
||||
visible: function() {
|
||||
return !obj.inTable;
|
||||
},
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
|
||||
type: 'int', group: gettext('Definition'), min: 10, max: 100,
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {fillfactor: null};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'), deps: ['index'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {condeferrable: false};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {condeferred: false};
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
if(isEmptyString(state.index)
|
||||
&& (_.isUndefined(state.columns) || _.isNull(state.columns) || state.columns.length < 1)) {
|
||||
setError('columns', gettext('Please specify columns for %s.', gettext('Primary key')));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -7,11 +7,13 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
|
||||
import UniqueConstraintSchema from './unique_constraint.ui';
|
||||
|
||||
define('pgadmin.node.unique_constraint', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's node class for index constraint node
|
||||
if (!pgBrowser.Nodes['unique_constraint']) {
|
||||
@ -83,571 +85,15 @@ define('pgadmin.node.unique_constraint', [
|
||||
}
|
||||
},
|
||||
|
||||
// Define the model for index constraint node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
spcname: undefined,
|
||||
index: undefined,
|
||||
fillfactor: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
columns: [],
|
||||
include: [],
|
||||
},
|
||||
|
||||
// Define the schema for the index constraint node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cellHeaderClasses:'width_percent_40',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'], editable: false,
|
||||
cellHeaderClasses:'width_percent_20',
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System unique constraint?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(m) {
|
||||
var name = m.get('name');
|
||||
if (!(name && name != '')) {
|
||||
setTimeout(function(){
|
||||
if(m.get('comment') && m.get('comment') !== '') {
|
||||
m.set('comment', null);
|
||||
}
|
||||
},10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
type: 'collection', group: gettext('Definition'),
|
||||
editable: false,
|
||||
cell: Backgrid.StringCell.extend({
|
||||
initialize: function() {
|
||||
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
|
||||
|
||||
var self = this,
|
||||
collection = this.model.get('columns');
|
||||
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.removeColumn);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
collection.on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
self.listenTo(collection, 'add', self.render);
|
||||
self.listenTo(collection, 'remove', self.render);
|
||||
},
|
||||
removeColumn: function(m) {
|
||||
var self = this,
|
||||
removedCols = self.model.get('columns').where(
|
||||
{column: m.get('name')}
|
||||
);
|
||||
|
||||
self.model.get('columns').remove(removedCols);
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
|
||||
var key = 'unique_constraint';
|
||||
setTimeout(function () {
|
||||
var constraints = self.model.top.get(key),
|
||||
removed = [];
|
||||
|
||||
constraints.each(function(constraint) {
|
||||
if (constraint.get('columns').length == 0) {
|
||||
removed.push(constraint);
|
||||
}
|
||||
});
|
||||
constraints.remove(removed);
|
||||
},100);
|
||||
},
|
||||
resetColOptions : function(m) {
|
||||
var self = this,
|
||||
updatedCols = self.model.get('columns').where(
|
||||
{column: m.previous('name')}
|
||||
);
|
||||
if (updatedCols.length > 0) {
|
||||
/*
|
||||
* Table column name has changed so update
|
||||
* column name in primary key as well.
|
||||
*/
|
||||
updatedCols[0].set(
|
||||
{'column': m.get('name')},
|
||||
{silent: true});
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
},
|
||||
formatter: {
|
||||
fromRaw: function (rawValue) {
|
||||
return rawValue.pluck('column').toString();
|
||||
},
|
||||
toRaw: function (val) { return val; },
|
||||
},
|
||||
render: function() {
|
||||
return Backgrid.StringCell.prototype.render.apply(this, arguments);
|
||||
},
|
||||
remove: function() {
|
||||
var tableCols = this.model.top.get('columns'),
|
||||
primary_key_col = this.model.get('columns');
|
||||
|
||||
if (primary_key_col) {
|
||||
primary_key_col.off('pgadmin:multicolumn:updated');
|
||||
}
|
||||
|
||||
this.stopListening(tableCols, 'remove' , self.removeColumn);
|
||||
this.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
|
||||
Backgrid.StringCell.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
canDelete: true, canAdd: true,
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
width: 'style',
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
keyPathAccessor: function(obj, path) {
|
||||
var res = obj;
|
||||
if(_.isArray(res)) {
|
||||
return _.map(res, function(o) { return o['column'];
|
||||
});
|
||||
}
|
||||
path = path.split('.');
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
if (_.isNull(res)) return null;
|
||||
if (_.isEmpty(path[i])) continue;
|
||||
if (!_.isUndefined(res[path[i]])) res = res[path[i]];
|
||||
}
|
||||
return _.isObject(res) && !_.isArray(res) ? null : res;
|
||||
},
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
var self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
self.model.get('columns').on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
},
|
||||
resetColOptions: function() {
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.custom_options();
|
||||
self.render.apply(self);
|
||||
}, 50);
|
||||
},
|
||||
custom_options: function() {
|
||||
// We will add all the columns entered by user in table model
|
||||
var columns = this.model.top.get('columns'),
|
||||
added_columns_from_tables = [];
|
||||
|
||||
if (columns.length > 0) {
|
||||
_.each(columns.models, function(m) {
|
||||
var col = m.get('name');
|
||||
if(!_.isUndefined(col) && !_.isNull(col)) {
|
||||
added_columns_from_tables.push(
|
||||
{label: col, value: col, image:'icon-column'}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set the values in to options so that user can select
|
||||
this.field.set('options', added_columns_from_tables);
|
||||
},
|
||||
onChange: function() {
|
||||
var self = this,
|
||||
model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
vals = this.getValueFromDOM(),
|
||||
collection = model.get(name),
|
||||
removed = [];
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
|
||||
/*
|
||||
* Iterate through all the values, and find out how many are already
|
||||
* present in the collection.
|
||||
*/
|
||||
collection.each(function(m) {
|
||||
var column = m.get('column'),
|
||||
idx = _.indexOf(vals, column);
|
||||
|
||||
if (idx > -1) {
|
||||
vals.splice(idx, 1);
|
||||
} else {
|
||||
removed.push(column);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Adding new values
|
||||
*/
|
||||
|
||||
_.each(vals, function(v) {
|
||||
var m = new (self.field.get('model'))(
|
||||
{column: v}, { silent: true,
|
||||
top: self.model.top,
|
||||
collection: collection,
|
||||
handler: collection,
|
||||
});
|
||||
|
||||
collection.add(m);
|
||||
});
|
||||
|
||||
/*
|
||||
* Removing unwanted!
|
||||
*/
|
||||
_.each(removed, function(v) {
|
||||
collection.remove(collection.where({column: v}));
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
remove: function() {
|
||||
if(this.model.handler) {
|
||||
var self = this,
|
||||
tableCols = self.model.top.get('columns');
|
||||
self.stopListening(tableCols, 'remove' , self.resetColOptions);
|
||||
self.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
self.model.get('columns').off('pgadmin:multicolumn:updated');
|
||||
|
||||
Backform.Select2Control.prototype.remove.apply(this, arguments);
|
||||
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.remove.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var index = this.model.get('index');
|
||||
if(!_.isUndefined(index) && index != '') {
|
||||
var col = this.model.get('columns');
|
||||
col.reset([], {silent: true});
|
||||
}
|
||||
return Backform.Select2Control.prototype.render.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
column: undefined,
|
||||
},
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
transform : function(data){
|
||||
var res = [];
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d.label, value: d.label, image:'icon-column'});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
select2:{allowClear:false},
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should be allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update columns of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
return (!_.isUndefined(index) && index != '');
|
||||
},
|
||||
},{
|
||||
id: 'include', label: gettext('Include columns'),
|
||||
type: 'array', group: gettext('Definition'),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'],
|
||||
visible: function(m) {
|
||||
/* In table properties, m.node_info is not available */
|
||||
m = m.top;
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 110000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
allowClear: false,
|
||||
width: 'style',
|
||||
multiple: true,
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
var self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
var tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
resetColOptions: function() {
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.custom_options();
|
||||
self.render.apply(self);
|
||||
}, 50);
|
||||
},
|
||||
custom_options: function() {
|
||||
// We will add all the columns entered by user in table model
|
||||
var columns = this.model.top.get('columns'),
|
||||
added_columns_from_tables = [];
|
||||
|
||||
if (columns.length > 0) {
|
||||
_.each(columns.models, function(m) {
|
||||
var col = m.get('name');
|
||||
if(!_.isUndefined(col) && !_.isNull(col)) {
|
||||
added_columns_from_tables.push(
|
||||
{label: col, value: col, image:'icon-column'}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set the values in to options so that user can select
|
||||
this.field.set('options', added_columns_from_tables);
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should be allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update columns of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('include', []);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: 'node-list-by-name', node: 'tablespace',
|
||||
deps: ['index'],
|
||||
select2:{allowClear:false},
|
||||
filter: function(m) {
|
||||
// Don't show pg_global tablespace in selection.
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return new UniqueConstraintSchema({
|
||||
columns: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData),
|
||||
spcname: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
|
||||
return (m.label != 'pg_global');
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
m = m.top || m;
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('spcname', '');
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'index', label: gettext('Index'),
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: Backform.NodeListByNameControl.extend({
|
||||
initialize:function() {
|
||||
Backform.NodeListByNameControl.prototype.initialize.apply(this,arguments);
|
||||
},
|
||||
}),
|
||||
select2:{allowClear:true}, node: 'index',
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then disable it
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't update index of existing index constraint.
|
||||
return !m.isNew();
|
||||
},
|
||||
// We will not show this field in Create Table mode
|
||||
visible: function(m) {
|
||||
return !_.isUndefined(m.top.node_info['table']);
|
||||
},
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
|
||||
type: 'int', group: gettext('Definition'), allowNull: true,
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('fillfactor', null);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'), deps: ['index'],
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update condeferrable of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
var index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferrable'))
|
||||
m.set('condeferrable', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
// If OID is undefined then user is trying to add
|
||||
// new constraint which should allowed for Unique
|
||||
return !_.isUndefined(m.get('oid'));
|
||||
}
|
||||
|
||||
// We can't update condeferred of existing index constraint.
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if condeferred is false or unselected.
|
||||
if(m.get('condeferrable') == true) {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferred'))
|
||||
m.set('condeferred', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
this.errorModel.clear();
|
||||
// Clear parent's error as well
|
||||
if (_.has(this, 'top')) {
|
||||
this.top.errorModel.clear();
|
||||
}
|
||||
|
||||
var columns = this.get('columns'),
|
||||
index = this.get('index');
|
||||
|
||||
if ((_.isUndefined(index) || String(index).replace(/^\s+|\s+$/g, '') == '') &&
|
||||
(_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
|
||||
var msg = gettext('Please specify columns for %s.', gettext('Unique constraint'));
|
||||
this.errorModel.set('columns', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
index: ()=>getNodeListByName('index', treeNodeInfo, itemNodeData),
|
||||
}, treeNodeInfo);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,273 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../../../static/js/SchemaView';
|
||||
|
||||
export default class UniqueConstraintSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
spcname: undefined,
|
||||
index: undefined,
|
||||
fillfactor: undefined,
|
||||
condeferrable: undefined,
|
||||
condeferred: undefined,
|
||||
columns: [],
|
||||
include: [],
|
||||
});
|
||||
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.nodeInfo = nodeInfo;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get inTable() {
|
||||
if(_.isUndefined(this.nodeInfo)) {
|
||||
return true;
|
||||
}
|
||||
return _.isUndefined(this.nodeInfo['table']);
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.fieldOptions.columns = columns;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cell: 'text',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'text',
|
||||
type: 'text' , mode: ['properties'], editable: false,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System primary key?'),
|
||||
cell:'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'multiline',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled: (state)=>{
|
||||
if(isEmptyString(state.name)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, depChange: (state)=>{
|
||||
if(isEmptyString(state.name)){
|
||||
return {comment: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
deps: ()=>{
|
||||
let ret = ['index'];
|
||||
if(obj.inTable) {
|
||||
ret.push(['columns']);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
/* If in table, sync up value with columns in table */
|
||||
let currColumns = state.columns || [];
|
||||
if(obj.inTable && source[0] == 'columns') {
|
||||
if(actionObj.type == SCHEMA_STATE_ACTIONS.DELETE_ROW) {
|
||||
let oldColumn = _.get(actionObj.oldState, actionObj.path.concat(actionObj.value));
|
||||
currColumns = _.filter(currColumns, (cc)=>cc.column != oldColumn.name);
|
||||
} else if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE) {
|
||||
let tabColPath = _.slice(actionObj.path, 0, -1);
|
||||
let oldColName = _.get(actionObj.oldState, tabColPath).name;
|
||||
let idx = _.findIndex(currColumns, (cc)=>cc.column == oldColName);
|
||||
if(idx > -1) {
|
||||
currColumns[idx].column = _.get(topState, tabColPath).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {columns: currColumns};
|
||||
},
|
||||
cell: ()=>({
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=>{
|
||||
/* remove the column key and pass as array */
|
||||
return _.join((backendVal||[]).map((singleVal)=>singleVal.column));
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
type: ()=>({
|
||||
type: 'select',
|
||||
optionsReloadBasis: obj.fieldOptions.columns?.map ? _.join(obj.fieldOptions.columns.map((c)=>c.label), ',') : null,
|
||||
options: obj.fieldOptions.columns,
|
||||
controlProps: {
|
||||
allowClear:false,
|
||||
multiple: true,
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=>{
|
||||
/* remove the column key and pass as array */
|
||||
return (backendVal||[]).map((singleVal)=>singleVal.column);
|
||||
},
|
||||
toRaw: (value)=>{
|
||||
/* take the array and convert to column key collection */
|
||||
return (value||[]).map((singleVal)=>({column: singleVal}));
|
||||
},
|
||||
},
|
||||
},
|
||||
}), group: gettext('Definition'),
|
||||
editable: false,
|
||||
readonly: function(state) {
|
||||
if(!obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
},{
|
||||
id: 'include', label: gettext('Include columns'),
|
||||
type: ()=>({
|
||||
type: 'select',
|
||||
options: this.fieldOptions.columns,
|
||||
controlProps: {
|
||||
multiple: true,
|
||||
},
|
||||
}), group: gettext('Definition'),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true,
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
visible: function() {
|
||||
/* In table properties, nodeInfo is not available */
|
||||
if(!_.isUndefined(this.nodeInfo) && !_.isUndefined(this.nodeInfo.server)
|
||||
&& !_.isUndefined(this.nodeInfo.server.version) &&
|
||||
this.nodeInfo.server.version >= 110000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
deps: ['index'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {include: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'select', group: gettext('Definition'),
|
||||
options: this.fieldOptions.spcname,
|
||||
deps: ['index'],
|
||||
controlProps:{allowClear:false},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {spcname: ''};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'index', label: gettext('Index'),
|
||||
mode: ['create'],
|
||||
type: 'select', group: gettext('Definition'),
|
||||
controlProps:{
|
||||
allowClear:true,
|
||||
options: this.fieldOptions.index,
|
||||
},
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
// We will not show this field in Create Table mode
|
||||
visible: function() {
|
||||
return !obj.inTable;
|
||||
},
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
|
||||
type: 'int', group: gettext('Definition'),
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {fillfactor: null};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'), deps: ['index'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {condeferrable: false};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
readonly: function() {
|
||||
if(!obj.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
// Disable if index is selected.
|
||||
return !(_.isUndefined(state.index) || state.index == '');
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(_.isUndefined(state.index) || state.index == '') {
|
||||
return {};
|
||||
} else {
|
||||
return {condeferred: false};
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
if(isEmptyString(state.index)
|
||||
&& (_.isUndefined(state.columns) || _.isNull(state.columns) || state.columns.length < 1)) {
|
||||
setError('columns', gettext('Please specify columns for %s.', gettext('Unique constraint')));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
import _ from 'lodash';
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { emptyValidator, isEmptyString } from '../../../../../../../../static/js/validators';
|
||||
|
||||
export class PartitionKeysSchema extends BaseUISchema {
|
||||
constructor(columns=[]) {
|
||||
super({
|
||||
key_type: 'column',
|
||||
});
|
||||
this.columns = columns;
|
||||
this.columnsReloadBasis = false;
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'key_type', label: gettext('Key type'), type:'select', editable: true,
|
||||
cell:'select', controlProps: {allowClear: false}, noEmpty: true,
|
||||
options:[{
|
||||
label: gettext('Column'), value: 'column',
|
||||
},{
|
||||
label: gettext('Expression'), value: 'expression',
|
||||
}],
|
||||
},{
|
||||
id: 'pt_column', label: gettext('Column'), type:'select',
|
||||
deps: ['key_type', ['columns']],
|
||||
depChange: (state, source)=>{
|
||||
if(state.key_type == 'expression' || source[0] == 'columns') {
|
||||
return {
|
||||
pt_column: undefined,
|
||||
};
|
||||
}
|
||||
},
|
||||
cell: ()=>({
|
||||
cell: 'select',
|
||||
optionsReloadBasis: _.join(obj.columns.map((c)=>c.label), ','),//obj.columnsReloadBasis,
|
||||
options: obj.columns,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
}
|
||||
}),
|
||||
editable: function(state) {
|
||||
if(state.key_type == 'expression') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'expression', label: gettext('Expression'), type:'text',
|
||||
cell: 'text', deps: ['key_type'],
|
||||
depChange: (state)=>{
|
||||
if(state.key_type == 'column') {
|
||||
return {
|
||||
expression: undefined,
|
||||
};
|
||||
}
|
||||
},
|
||||
editable: function(state) {
|
||||
if(state.key_type == 'expression') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let errmsg;
|
||||
if(state.key_type == 'column') {
|
||||
errmsg = emptyValidator('Partition key column', state.pt_column);
|
||||
if(errmsg) {
|
||||
setError('pt_column', errmsg);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
errmsg = emptyValidator('Partition key expression', state.expression);
|
||||
if(errmsg) {
|
||||
setError('expression', errmsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export class PartitionsSchema extends BaseUISchema {
|
||||
constructor(nodeInfo) {
|
||||
super({
|
||||
oid: undefined,
|
||||
is_attach: false,
|
||||
partition_name: undefined,
|
||||
is_default: undefined,
|
||||
values_from: undefined,
|
||||
values_to: undefined,
|
||||
values_in: undefined,
|
||||
values_modulus: undefined,
|
||||
values_remainder: undefined,
|
||||
is_sub_partitioned: false,
|
||||
sub_partition_type: 'range',
|
||||
});
|
||||
|
||||
this.subPartitionsObj = new PartitionKeysSchema();
|
||||
this.nodeInfo = nodeInfo;
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
this.subPartitionsObj.changeColumnOptions(columns);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'oid', label: gettext('OID'), type: 'text',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'is_attach', label:gettext('Operation'), cell: 'select', type: 'select',
|
||||
minWidth: 75, options: [
|
||||
{label: gettext('Attach'), value: true},
|
||||
{label: gettext('Create'), value: false},
|
||||
],
|
||||
editable: function(state) {
|
||||
if(obj.isNew(state) && !obj.top.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.isNew(state) && !obj.top.isNew()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'partition_name', label: gettext('Name'), type: 'text', cell:'text',
|
||||
minWidth: 80, editable: function(state) {
|
||||
if(obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.isNew(state)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, noEmpty: true,
|
||||
},{
|
||||
id: 'is_default', label: gettext('Default'), type: 'switch', cell:'switch',
|
||||
minWidth: 55, min_version: 110000,
|
||||
editable: function(state) {
|
||||
if(obj.top && (obj.top.sessData.partition_type == 'range' ||
|
||||
obj.top.sessData.partition_type == 'list') && obj.isNew(state)
|
||||
&& obj.getServerVersion() >= 110000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && (obj.top.sessData.partition_type == 'range' ||
|
||||
obj.top.sessData.partition_type == 'list') && obj.isNew(state)
|
||||
&& obj.getServerVersion() >= 110000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'values_from', label: gettext('From'), type:'text', cell: 'text',
|
||||
minWidth: 80, deps: ['is_default'],
|
||||
editable: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'range' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'range' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'values_to', label: gettext('To'), type:'text', cell: 'text',
|
||||
minWidth: 80, deps: ['is_default'],
|
||||
editable: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'range' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'range' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'values_in', label: gettext('In'), type:'text', cell: 'text',
|
||||
minWidth: 80, deps: ['is_default'],
|
||||
editable: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'list' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'list' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'values_modulus', label: gettext('Modulus'), type:'int', cell: 'int',
|
||||
minWidth: 80,
|
||||
editable: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'hash' && obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'hash' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'values_remainder', label: gettext('Remainder'), type:'int', cell: 'int',
|
||||
minWidth: 80,
|
||||
editable: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'hash' && obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
if(obj.top && obj.top.sessData.partition_type == 'hash' && obj.isNew(state)
|
||||
&& state.is_default !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'is_sub_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
|
||||
group: 'Partition', type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
deps: ['is_attach'], readonly: (state)=>{
|
||||
if(!obj.isNew(state)) {
|
||||
return true;
|
||||
}
|
||||
if(state.is_attach) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(state.is_attach) {
|
||||
return {is_sub_partitioned: false};
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'sub_partition_type', label:gettext('Partition Type'),
|
||||
editable: false, type: 'select', controlProps: {allowClear: false},
|
||||
group: 'Partition', deps: ['is_sub_partitioned'],
|
||||
options: function() {
|
||||
var options = [{
|
||||
label: gettext('Range'), value: 'range',
|
||||
},{
|
||||
label: gettext('List'), value: 'list',
|
||||
}];
|
||||
|
||||
if(obj.getServerVersion() >= 110000) {
|
||||
options.push({
|
||||
label: gettext('Hash'), value: 'hash',
|
||||
});
|
||||
}
|
||||
return Promise.resolve(options);
|
||||
},
|
||||
visible: (state)=>obj.isNew(state),
|
||||
readonly: (state)=>!obj.isNew(state),
|
||||
disabled: (state)=>!state.is_sub_partitioned,
|
||||
},{
|
||||
id: 'sub_partition_keys', label:gettext('Partition Keys'),
|
||||
schema: this.subPartitionsObj,
|
||||
editable: true, type: 'collection',
|
||||
group: 'Partition', mode: ['properties', 'create', 'edit'],
|
||||
deps: ['is_sub_partitioned', 'sub_partition_type', ['typname']],
|
||||
canEdit: false, canDelete: true,
|
||||
canAdd: function(state) {
|
||||
if (obj.isNew(state) && state.is_sub_partitioned)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
canAddRow: function(state) {
|
||||
let columnsExist = false;
|
||||
let columns = obj.top.sessData.columns;
|
||||
|
||||
var maxRowCount = 1000;
|
||||
if (state.sub_partition_type && state.sub_partition_type == 'list')
|
||||
maxRowCount = 1;
|
||||
|
||||
if (columns?.length > 0) {
|
||||
columnsExist = _.some(_.map(columns, 'name'));
|
||||
}
|
||||
|
||||
if(state.sub_partition_keys) {
|
||||
return state.sub_partition_keys.length < maxRowCount && columnsExist;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
if(topState.typname != actionObj.oldState.typname) {
|
||||
return {
|
||||
sub_partition_keys: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
visible: (state)=>obj.isNew(state),
|
||||
},{
|
||||
id: 'sub_partition_scheme', label: gettext('Partition Scheme'),
|
||||
group: 'Partition', mode: ['edit'],
|
||||
type: (state)=>({
|
||||
type: 'note',
|
||||
text: state.sub_partition_scheme,
|
||||
}),
|
||||
visible: function(state) {
|
||||
if(obj.isNew(state) && !isEmptyString(state.sub_partition_scheme)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let msg;
|
||||
if (state.is_sub_partitioned && this.isNew(state) &&
|
||||
(!state.sub_partition_keys || state.sub_partition_keys && state.sub_partition_keys.length <= 0)) {
|
||||
msg = gettext('Please specify at least one key for partitioned table.');
|
||||
setError('sub_partition_keys', msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
let partitionType = this.top.sessData.partition_type;
|
||||
if (partitionType === 'range') {
|
||||
if (!state.is_default && isEmptyString(state.values_from)) {
|
||||
msg = gettext('For range partition From field cannot be empty.');
|
||||
setError('values_from', msg);
|
||||
return true;
|
||||
} else if (!state.is_default && isEmptyString(state.values_to)) {
|
||||
msg = gettext('For range partition To field cannot be empty.');
|
||||
setError('values_to', msg);
|
||||
return true;
|
||||
}
|
||||
} else if (partitionType === 'list') {
|
||||
if (!state.is_default && isEmptyString(state.values_in)) {
|
||||
msg = gettext('For list partition In field cannot be empty.');
|
||||
setError('values_in', msg);
|
||||
return true;
|
||||
}
|
||||
} else if (partitionType === 'hash') {
|
||||
if(isEmptyString(state.values_modulus)) {
|
||||
msg = gettext('For hash partition Modulus field cannot be empty.');
|
||||
setError('values_modulus', msg);
|
||||
return true;
|
||||
} if(isEmptyString(state.values_remainder)) {
|
||||
msg = gettext('For hash partition Remainder field cannot be empty.');
|
||||
setError('values_remainder', msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,873 @@
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import SecLabelSchema from 'top/browser/server_groups/servers/static/js/sec_label.ui';
|
||||
import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import PrimaryKeySchema from '../../constraints/index_constraint/static/js/primary_key.ui';
|
||||
import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../static/js/SchemaView';
|
||||
import { PartitionKeysSchema, PartitionsSchema } from './partition.utils.ui';
|
||||
import { pgAlertify } from '../../../../../../../../static/js/helpers/legacyConnector';
|
||||
import CheckConstraintSchema from '../../constraints/check_constraint/static/js/check_constraint.ui';
|
||||
import UniqueConstraintSchema from '../../constraints/index_constraint/static/js/unique_constraint.ui';
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../static/js/node_ajax';
|
||||
import { getNodeColumnSchema } from '../../columns/static/js/column.ui';
|
||||
import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui';
|
||||
import { getNodeForeignKeySchema } from '../../constraints/foreign_key/static/js/foreign_key.ui';
|
||||
import { getNodeExclusionConstraintSchema } from '../../constraints/exclusion_constraint/static/js/exclusion_constraint.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
|
||||
|
||||
export function getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
||||
const spcname = ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
|
||||
return (m.label != 'pg_global');
|
||||
});
|
||||
|
||||
let tableNode = pgBrowser.Nodes['table'];
|
||||
|
||||
return new TableSchema(
|
||||
{
|
||||
relowner: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
|
||||
schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {
|
||||
cacheLevel: 'database',
|
||||
cacheNode: 'database',
|
||||
}, (d)=>{
|
||||
// If schema name start with pg_* then we need to exclude them
|
||||
if(d && d.label.match(/^pg_/))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
spcname: spcname,
|
||||
coll_inherits: ()=>getNodeAjaxOptions('get_inherits', tableNode, treeNodeInfo, itemNodeData),
|
||||
typname: ()=>getNodeAjaxOptions('get_oftype', tableNode, treeNodeInfo, itemNodeData),
|
||||
like_relation: ()=>getNodeAjaxOptions('get_relations', tableNode, treeNodeInfo, itemNodeData),
|
||||
},
|
||||
treeNodeInfo,
|
||||
{
|
||||
columns: ()=>getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser),
|
||||
vacuum_settings: ()=>getNodeVacuumSettingsSchema(tableNode, treeNodeInfo, itemNodeData),
|
||||
constraints: ()=>new ConstraintsSchema(
|
||||
treeNodeInfo,
|
||||
()=>getNodeForeignKeySchema(treeNodeInfo, itemNodeData, pgBrowser, true),
|
||||
()=>getNodeExclusionConstraintSchema(treeNodeInfo, itemNodeData, pgBrowser, true),
|
||||
{spcname: spcname},
|
||||
),
|
||||
},
|
||||
(privileges)=>getNodePrivilegeRoleSchema(tableNode, treeNodeInfo, itemNodeData, privileges),
|
||||
(params)=>{
|
||||
return getNodeAjaxOptions('get_columns', tableNode, treeNodeInfo, itemNodeData, {urlParams: params, useCache:false});
|
||||
},
|
||||
{
|
||||
relowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
|
||||
schema: treeNodeInfo.schema._label,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export class ConstraintsSchema extends BaseUISchema {
|
||||
constructor(nodeInfo, getFkObj, getExConsObj, otherOptions) {
|
||||
super();
|
||||
this.primaryKeyObj = new PrimaryKeySchema({
|
||||
spcname: otherOptions.spcname,
|
||||
}, nodeInfo);
|
||||
this.fkObj = getFkObj();
|
||||
this.uniqueConsObj = new UniqueConstraintSchema({
|
||||
spcname: otherOptions.spcname,
|
||||
}, nodeInfo);
|
||||
this.exConsObj = getExConsObj();
|
||||
}
|
||||
|
||||
changeColumnOptions(colOptions) {
|
||||
this.primaryKeyObj.changeColumnOptions(colOptions);
|
||||
this.fkObj.changeColumnOptions(colOptions);
|
||||
this.uniqueConsObj.changeColumnOptions(colOptions);
|
||||
this.exConsObj.changeColumnOptions(colOptions);
|
||||
}
|
||||
|
||||
anyColumnAdded(state) {
|
||||
return _.some(_.map(state.columns, 'name'));
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'primary_key', label: '',
|
||||
schema: this.primaryKeyObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Primary Key'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned', 'typname'],
|
||||
columns : ['name', 'columns'],
|
||||
canAdd: function(state) {
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
canAddRow: function(state) {
|
||||
return ((state.primary_key||[]).length < 1 && obj.anyColumnAdded(state));
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) {
|
||||
return {primary_key: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'foreign_key', label: '',
|
||||
schema: this.fkObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Foreign Key'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned', 'columns'],
|
||||
canAdd: function(state) {
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
columns : ['name', 'columns','references_table_name'],
|
||||
canAddRow: obj.anyColumnAdded,
|
||||
depChange: (state)=>{
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) {
|
||||
return {foreign_key: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'check_constraint', label: '',
|
||||
schema: new CheckConstraintSchema(),
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Check'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
canAdd: true,
|
||||
columns : ['name', 'consrc'],
|
||||
},{
|
||||
id: 'unique_constraint', label: '',
|
||||
schema: this.primaryKeyObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Unique'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned', 'typname'],
|
||||
columns : ['name', 'columns'],
|
||||
canAdd: function(state) {
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
canAddRow: obj.anyColumnAdded,
|
||||
depChange: (state)=>{
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) {
|
||||
return {unique_constraint: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'exclude_constraint', label: '',
|
||||
schema: this.exConsObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Exclude'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
columns : ['name', 'columns', 'constraint'],
|
||||
canAdd: function(state) {
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
canAddRow: obj.anyColumnAdded,
|
||||
depChange: (state)=>{
|
||||
if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) {
|
||||
return {exclude_constraint: []};
|
||||
}
|
||||
},
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export class LikeSchema extends BaseUISchema {
|
||||
constructor(likeRelationOpts) {
|
||||
super();
|
||||
this.likeRelationOpts = likeRelationOpts;
|
||||
}
|
||||
|
||||
isLikeDisable(state) {
|
||||
if(!this.top.inSchemaWithModelCheck(state) && isEmptyString(state.typname)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isRelationDisable(state) {
|
||||
if(isEmptyString(state.like_relation)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
resetVals(state) {
|
||||
if(this.isRelationDisable(state) && this.top.isNew()) {
|
||||
return {
|
||||
like_default_value: false,
|
||||
like_constraints: false,
|
||||
like_indexes: false,
|
||||
like_storage: false,
|
||||
like_comments: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [
|
||||
{
|
||||
id: 'like_relation', label: gettext('Relation'),
|
||||
type: 'select', mode: ['create'], deps: ['typname', 'like_relation'],
|
||||
options: obj.likeRelationOpts,
|
||||
disabled: obj.isLikeDisable,
|
||||
depChange: (state, source)=>{
|
||||
if(source == 'typname' && !isEmptyString(state.typname)) {
|
||||
state.like_relation = null;
|
||||
return {
|
||||
like_relation: null,
|
||||
...obj.resetVals(state),
|
||||
};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'like_default_value', label: gettext('With default values?'),
|
||||
type: 'switch', mode: ['create'], deps: ['like_relation'],
|
||||
disabled: this.isRelationDisable, depChange: (...args)=>obj.resetVals(...args),
|
||||
},{
|
||||
id: 'like_constraints', label: gettext('With constraints?'),
|
||||
type: 'switch', mode: ['create'], deps: ['like_relation'],
|
||||
disabled: this.isRelationDisable, depChange: (...args)=>obj.resetVals(...args),
|
||||
},{
|
||||
id: 'like_indexes', label: gettext('With indexes?'),
|
||||
type: 'switch', mode: ['create'], deps: ['like_relation'],
|
||||
disabled: this.isRelationDisable, depChange: (...args)=>obj.resetVals(...args),
|
||||
},{
|
||||
id: 'like_storage', label: gettext('With storage?'),
|
||||
type: 'switch', mode: ['create'], deps: ['like_relation'],
|
||||
disabled: this.isRelationDisable, depChange: (...args)=>obj.resetVals(...args),
|
||||
},{
|
||||
id: 'like_comments', label: gettext('With comments?'),
|
||||
type: 'switch', mode: ['create'], deps: ['like_relation'],
|
||||
disabled: this.isRelationDisable, depChange: (...args)=>obj.resetVals(...args),
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default class TableSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo, schemas, getPrivilegeRoleSchema, getColumns, initValues) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
spcoid: undefined,
|
||||
spcname: undefined,
|
||||
relowner: undefined,
|
||||
relacl: undefined,
|
||||
relhasoids: undefined,
|
||||
relhassubclass: undefined,
|
||||
reltuples: undefined,
|
||||
description: undefined,
|
||||
conname: undefined,
|
||||
conkey: undefined,
|
||||
isrepl: undefined,
|
||||
triggercount: undefined,
|
||||
relpersistence: undefined,
|
||||
fillfactor: undefined,
|
||||
toast_tuple_target: undefined,
|
||||
parallel_workers: undefined,
|
||||
reloftype: undefined,
|
||||
typname: undefined,
|
||||
labels: undefined,
|
||||
providers: undefined,
|
||||
is_sys_table: undefined,
|
||||
coll_inherits: [],
|
||||
hastoasttable: true,
|
||||
toast_autovacuum_enabled: 'x',
|
||||
autovacuum_enabled: 'x',
|
||||
primary_key: [],
|
||||
foreign_key: [],
|
||||
partitions: [],
|
||||
partition_type: 'range',
|
||||
is_partitioned: false,
|
||||
columns: [],
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.schemas = schemas;
|
||||
this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.getColumns = getColumns;
|
||||
|
||||
this.partitionKeysObj = new PartitionKeysSchema();
|
||||
this.partitionsObj = new PartitionsSchema(this.nodeInfo);
|
||||
this.constraintsObj = this.schemas.constraints();
|
||||
this.columnsSchema = this.schemas.columns();
|
||||
this.vacuumSettingsSchema = this.schemas.vacuum_settings();
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
initialise(state) {
|
||||
this.changeColumnOptions(state.columns);
|
||||
}
|
||||
|
||||
inSchemaWithModelCheck(state) {
|
||||
if(this.nodeInfo && 'schema' in this.nodeInfo) {
|
||||
return !this.isNew(state);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getTableOid(tabName) {
|
||||
// Here we will fetch the table oid from table name
|
||||
// iterate over list to find table oid
|
||||
for(const t of this.inheritedTableList) {
|
||||
if(t.label === tabName) {
|
||||
return t.tid;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for column grid when to Add
|
||||
canAddRowColumns(state) {
|
||||
if(!this.inCatalog()) {
|
||||
// if of_type then disable add in grid
|
||||
if (!isEmptyString(state.typname)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for column grid when to edit/delete (for each row)
|
||||
canEditDeleteRowColumns(colstate) {
|
||||
if (!isEmptyString(colstate.inheritedfrom)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isPartitioned(state) {
|
||||
if(state.is_partitioned) {
|
||||
return true;
|
||||
}
|
||||
return this.inCatalog();
|
||||
}
|
||||
|
||||
changeColumnOptions(columns) {
|
||||
let colOptions = (columns||[]).map((c)=>({label: c.name, value: c.name, image:'icon-column'}));
|
||||
this.constraintsObj.changeColumnOptions(colOptions);
|
||||
this.partitionKeysObj.changeColumnOptions(colOptions);
|
||||
this.partitionsObj.changeColumnOptions(colOptions);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text', noEmpty: true,
|
||||
mode: ['properties', 'create', 'edit'], readonly: this.inCatalog,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'relowner', label: gettext('Owner'), type: 'select',
|
||||
options: this.fieldOptions.relowner, noEmpty: true,
|
||||
mode: ['properties', 'create', 'edit'], controlProps: {allowClear: false},
|
||||
readonly: this.inCatalog,
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), type: 'select',
|
||||
options: this.fieldOptions.schema, noEmpty: true,
|
||||
mode: ['create', 'edit'],
|
||||
readonly: this.inCatalog,
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'select', options: this.fieldOptions.spcname,
|
||||
mode: ['properties', 'create', 'edit'], deps: ['is_partitioned'],
|
||||
readonly: this.inCatalog,
|
||||
},{
|
||||
id: 'partition', type: 'group', label: gettext('Partitions'),
|
||||
mode: ['edit', 'create'], min_version: 100000,
|
||||
visible: function(state) {
|
||||
// Always show in case of create mode
|
||||
if (obj.isNew(state) || state.is_partitioned)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'is_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
min_version: 100000,
|
||||
readonly: function(state) {
|
||||
if (!obj.isNew(state))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'is_sys_table', label: gettext('System table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties'],
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'], disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'coll_inherits', label: gettext('Inherited from table(s)'),
|
||||
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,
|
||||
optionsLoaded: (res)=>obj.inheritedTableList=res,
|
||||
disabled: (state)=>{
|
||||
if(state.adding_inherit_cols || state.is_partitioned){
|
||||
return true;
|
||||
}
|
||||
if(!obj.inCatalog() && isEmptyString(state.typname)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE && actionObj.path[0] == 'coll_inherits') {
|
||||
return {adding_inherit_cols: true};
|
||||
}
|
||||
if(source[0] === 'is_partitioned' && state.is_partitioned) {
|
||||
for(const collTable of state.coll_inherits || []) {
|
||||
let removeOid = this.getTableOid(collTable);
|
||||
_.remove(state.columns, (col)=>col.inheritedid==removeOid);
|
||||
}
|
||||
|
||||
return {
|
||||
coll_inherits: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
deferredDepChange: (state, source, topState, actionObj)=>{
|
||||
return new Promise((resolve)=>{
|
||||
// current table list and previous table list
|
||||
let newColInherits = state.coll_inherits || [];
|
||||
let oldColInherits = actionObj.oldState.coll_inherits || [];
|
||||
|
||||
let tabName;
|
||||
let tabColsResponse;
|
||||
|
||||
// Add columns logic
|
||||
// If new table is added in list
|
||||
if(newColInherits.length > 1 && newColInherits.length > oldColInherits.length) {
|
||||
// Find newly added table from current list
|
||||
tabName = _.difference(newColInherits, oldColInherits);
|
||||
tabColsResponse = obj.getColumns({tid: this.getTableOid(tabName[0])});
|
||||
} else if (newColInherits.length == 1) {
|
||||
// First table added
|
||||
tabColsResponse = obj.getColumns({tid: this.getTableOid(newColInherits[0])});
|
||||
}
|
||||
|
||||
if(tabColsResponse) {
|
||||
tabColsResponse.then((res)=>{
|
||||
resolve((state)=>{
|
||||
let finalCols = res.map((col)=>obj.columnsSchema.getNewData(col));
|
||||
finalCols = [...state.columns, ...finalCols];
|
||||
obj.changeColumnOptions(finalCols);
|
||||
return {
|
||||
adding_inherit_cols: false,
|
||||
columns: finalCols,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove columns logic
|
||||
let removeOid;
|
||||
if(newColInherits.length > 0 && newColInherits.length < oldColInherits.length) {
|
||||
// Find deleted table from previous list
|
||||
tabName = _.difference(oldColInherits, newColInherits);
|
||||
removeOid = this.getTableOid(tabName[0]);
|
||||
} else if (oldColInherits.length === 1 && newColInherits.length < 1) {
|
||||
// We got last table from list
|
||||
tabName = oldColInherits[0];
|
||||
removeOid = this.getTableOid(tabName);
|
||||
}
|
||||
if(removeOid) {
|
||||
resolve((state)=>{
|
||||
let finalCols = state.columns;
|
||||
_.remove(state.columns, (col)=>col.inheritedid==removeOid);
|
||||
obj.changeColumnOptions(finalCols);
|
||||
return {
|
||||
adding_inherit_cols: false,
|
||||
columns: finalCols
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},{
|
||||
id: 'advanced', label: gettext('Advanced'), type: 'group',
|
||||
// visible: ShowAdvancedTab.show_advanced_tab,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'rlspolicy', label: gettext('RLS Policy?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties','edit', 'create'],
|
||||
group: 'advanced', min_version: 90600,
|
||||
depChange: (state)=>{
|
||||
if (state.rlspolicy && this.origData.rlspolicy != state.rlspolicy) {
|
||||
pgAlertify().alert(
|
||||
gettext('Check Policy?'),
|
||||
gettext('Please check if any policy exist. If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified by other users')
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'forcerlspolicy', label: gettext('Force RLS Policy?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties','edit', 'create'], deps: ['rlspolicy'],
|
||||
group: 'advanced', min_version: 90600,
|
||||
disabled: function(state) {
|
||||
return !state.rlspolicy;
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(!state.rlspolicy) {
|
||||
return {forcerlspolicy: false};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'replica_identity', label: gettext('Replica Identity'),
|
||||
group: gettext('advanced'), type: 'text',mode: ['edit', 'properties'],
|
||||
}, {
|
||||
id: 'coll_inherits', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', group: 'advanced', mode: ['properties'],
|
||||
},{
|
||||
id: 'inherited_tables_cnt', label: gettext('Inherited tables count'),
|
||||
type: 'text', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
// Tab control for columns
|
||||
id: 'columns', label: gettext('Columns'), type: 'collection',
|
||||
group: gettext('Columns'),
|
||||
schema: this.columnsSchema,
|
||||
mode: ['create', 'edit'],
|
||||
disabled: function() {
|
||||
if(this.nodeInfo && 'catalog' in this.nodeInfo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
deps: ['typname', 'is_partitioned'],
|
||||
depChange: (state, source)=>{
|
||||
if(source[0] === 'columns') {
|
||||
obj.changeColumnOptions(state.columns);
|
||||
}
|
||||
},
|
||||
canAdd: this.canAddRowColumns,
|
||||
canEdit: true, canDelete: true,
|
||||
// For each row edit/delete button enable/disable
|
||||
canEditRow: this.canEditDeleteRowColumns,
|
||||
canDeleteRow: this.canEditDeleteRowColumns,
|
||||
uniqueCol : ['name'],
|
||||
columns : ['name' , 'cltype', 'attlen', 'attprecision', 'attnotnull', 'is_primary_key'],
|
||||
allowMultipleEmptyRow: false,
|
||||
},{
|
||||
// Here we will create tab control for constraints
|
||||
type: 'nested-tab', group: gettext('Constraints'),
|
||||
mode: ['edit', 'create'],
|
||||
schema: obj.constraintsObj,
|
||||
},{
|
||||
id: 'typname', label: gettext('Of type'), type: 'select',
|
||||
mode: ['properties', 'create', 'edit'], group: 'advanced', deps: ['coll_inherits'],
|
||||
disabled: (state)=>{
|
||||
if(!obj.inSchemaWithModelCheck(state) && isEmptyString(state.coll_inherits)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, options: this.fieldOptions.typname, optionsLoaded: (res)=>{
|
||||
obj.ofTypeTables = res;
|
||||
},
|
||||
depChange: (state, source)=>{
|
||||
if(source[0] == 'typname' && !isEmptyString(state.typname)) {
|
||||
return {
|
||||
columns: [],
|
||||
primary_key: []
|
||||
};
|
||||
}
|
||||
},
|
||||
deferredDepChange: (state, source, topState, actionObj)=>{
|
||||
const setColumns = (resolve)=>{
|
||||
let finalCols = [];
|
||||
if(!isEmptyString(state.typname)) {
|
||||
let typeTable = _.find(obj.ofTypeTables||[], (t)=>t.label==state.typname);
|
||||
finalCols = typeTable.oftype_columns;
|
||||
}
|
||||
resolve(()=>{
|
||||
obj.changeColumnOptions(finalCols);
|
||||
return {
|
||||
columns: finalCols,
|
||||
};
|
||||
});
|
||||
};
|
||||
if(!isEmptyString(state.typname) && isEmptyString(actionObj.oldState.typname)) {
|
||||
return new Promise((resolve)=>{
|
||||
pgAlertify().confirm(
|
||||
gettext('Remove column definitions?'),
|
||||
gettext('Changing \'Of type\' will remove column definitions.'),
|
||||
function () {
|
||||
setColumns(resolve);
|
||||
},
|
||||
function() {
|
||||
resolve(()=>{
|
||||
return {
|
||||
typname: null,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if(state.typname != actionObj.oldState.typname) {
|
||||
return new Promise((resolve)=>{
|
||||
setColumns(resolve);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(()=>{});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), type: 'int',
|
||||
mode: ['create', 'edit'], min: 10, max: 100,
|
||||
group: 'advanced',
|
||||
disabled: obj.isPartitioned,
|
||||
},{
|
||||
id: 'toast_tuple_target', label: gettext('Toast tuple target'), type: 'int',
|
||||
mode: ['create', 'edit'], min: 128, min_version: 110000,
|
||||
group: 'advanced',
|
||||
disabled: obj.isPartitioned,
|
||||
},{
|
||||
id: 'parallel_workers', label: gettext('Parallel workers'), type: 'int',
|
||||
mode: ['create', 'edit'], group: 'advanced', min_version: 90600,
|
||||
disabled: obj.isPartitioned,
|
||||
},
|
||||
{
|
||||
id: 'relhasoids', label: gettext('Has OIDs?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
group: 'advanced',
|
||||
disabled: function() {
|
||||
if(obj.getServerVersion() >= 120000) {
|
||||
return true;
|
||||
}
|
||||
return obj.inCatalog();
|
||||
},
|
||||
},{
|
||||
id: 'relpersistence', label: gettext('Unlogged?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
disabled: obj.inSchemaWithModelCheck,
|
||||
group: 'advanced',
|
||||
},{
|
||||
id: 'conname', label: gettext('Primary key'), cell: 'text',
|
||||
type: 'text', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'reltuples', label: gettext('Rows (estimated)'), cell: 'text',
|
||||
type: 'text', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'rows_cnt', label: gettext('Rows (counted)'), cell: 'text',
|
||||
type: 'text', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
formatter: {
|
||||
fromRaw: ()=>{
|
||||
return 0;
|
||||
},
|
||||
toRaw: (backendVal)=>{
|
||||
return backendVal;
|
||||
},
|
||||
},
|
||||
},{
|
||||
id: 'relhassubclass', label: gettext('Inherits tables?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
type: 'nested-fieldset', label: gettext('Like'),
|
||||
group: 'advanced', mode: ['create'],
|
||||
schema: new LikeSchema(this.fieldOptions.like_relation),
|
||||
},{
|
||||
id: 'partition_type', label:gettext('Partition Type'),
|
||||
editable: false, type: 'select', controlProps: {allowClear: false},
|
||||
group: 'partition', deps: ['is_partitioned'],
|
||||
options: function() {
|
||||
var options = [{
|
||||
label: gettext('Range'), value: 'range',
|
||||
},{
|
||||
label: gettext('List'), value: 'list',
|
||||
}];
|
||||
|
||||
if(obj.getServerVersion() >= 110000) {
|
||||
options.push({
|
||||
label: gettext('Hash'), value: 'hash',
|
||||
});
|
||||
}
|
||||
return Promise.resolve(options);
|
||||
},
|
||||
mode:['create'],
|
||||
min_version: 100000,
|
||||
disabled: function(state) {
|
||||
if (!state.is_partitioned)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
readonly: function(state) {return !obj.isNew(state);},
|
||||
},
|
||||
{
|
||||
id: 'partition_keys', label:gettext('Partition Keys'),
|
||||
schema: obj.partitionKeysObj,
|
||||
editable: true, type: 'collection',
|
||||
group: 'partition', mode: ['create'],
|
||||
deps: ['is_partitioned', 'partition_type', 'typname'],
|
||||
canEdit: false, canDelete: true,
|
||||
canAdd: function(state) {
|
||||
if (obj.isNew(state) && state.is_partitioned)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
canAddRow: function(state) {
|
||||
let columnsExist = false;
|
||||
|
||||
var maxRowCount = 1000;
|
||||
if (state.partition_type && state.partition_type == 'list')
|
||||
maxRowCount = 1;
|
||||
|
||||
if (state.columns?.length > 0) {
|
||||
columnsExist = _.some(_.map(state.columns, 'name'));
|
||||
}
|
||||
|
||||
if(state.partition_keys) {
|
||||
return state.partition_keys.length < maxRowCount && columnsExist;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, min_version: 100000,
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
if(state.typname != actionObj.oldState.typname) {
|
||||
return {
|
||||
partition_keys: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'partition_scheme', label: gettext('Partition Scheme'),
|
||||
group: 'partition', mode: ['edit'],
|
||||
type: (state)=>({
|
||||
type: 'note',
|
||||
text: state.partition_scheme || '',
|
||||
}),
|
||||
min_version: 100000,
|
||||
},
|
||||
{
|
||||
id: 'partition_key_note', label: gettext('Partition Keys'),
|
||||
type: 'note', group: 'partition', mode: ['create'],
|
||||
text: [
|
||||
'<ul><li>',
|
||||
gettext('Partition table supports two types of keys:'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Column: '), '</strong>',
|
||||
gettext('User can select any column from the list of available columns.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Expression: '), '</strong>',
|
||||
gettext('User can specify expression to create partition key.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Example: '), '</strong>',
|
||||
gettext('Let\'s say, we want to create a partition table based per year for the column \'saledate\', having datatype \'date/timestamp\', then we need to specify the expression as \'extract(YEAR from saledate)\' as partition key.'),
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
min_version: 100000,
|
||||
},
|
||||
{
|
||||
id: 'partitions', label: gettext('Partitions'),
|
||||
schema: this.partitionsObj,
|
||||
editable: true, type: 'collection',
|
||||
group: 'partition', mode: ['edit', 'create'],
|
||||
deps: ['is_partitioned', 'partition_type', 'typname'],
|
||||
depChange: (state, source)=>{
|
||||
if(['is_partitioned', 'partition_type', 'typname'].indexOf(source[0]) >= 0 && obj.isNew(state)){
|
||||
return {'partitions': []};
|
||||
}
|
||||
},
|
||||
canEdit: true, canDelete: true,
|
||||
customDeleteTitle: gettext('Detach Partition'),
|
||||
customDeleteMsg: gettext('Are you sure you wish to detach this partition?'),
|
||||
columns:['is_attach', 'partition_name', 'is_default', 'values_from', 'values_to', 'values_in', 'values_modulus', 'values_remainder'],
|
||||
canAdd: function(state) {
|
||||
if (state.is_partitioned)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
min_version: 100000,
|
||||
},
|
||||
{
|
||||
id: 'partition_note', label: gettext('Partitions'),
|
||||
type: 'note', group: 'partition', mode: ['create'],
|
||||
text: [
|
||||
'<ul><li>',
|
||||
'<strong>', gettext('Create a table: '), '</strong>',
|
||||
gettext('User can create multiple partitions while creating new partitioned table. Operation switch is disabled in this scenario.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Edit existing table: '), '</strong>',
|
||||
gettext('User can create/attach/detach multiple partitions. In attach operation user can select table from the list of suitable tables to be attached.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Default: '), '</strong>',
|
||||
gettext('The default partition can store rows that do not fall into any existing partition’s range or list.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('From/To/In input: '), '</strong>',
|
||||
gettext('From/To/In input: Values for these fields must be quoted with single quote. For more than one partition key values must be comma(,) separated.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Example: From/To: '), '</strong>',
|
||||
gettext('Enabled for range partition. Consider partitioned table with multiple keys of type Integer, then values should be specified like \'100\',\'200\'.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('In: '), '</strong>',
|
||||
gettext('Enabled for list partition. Values must be comma(,) separated and quoted with single quote.'),
|
||||
'</li><li>',
|
||||
'<strong>', gettext('Modulus/Remainder: '), '</strong>',
|
||||
gettext('Enabled for hash partition.'),
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
min_version: 100000,
|
||||
},
|
||||
{
|
||||
// Here - we will create tab control for storage parameters
|
||||
// (auto vacuum).
|
||||
type: 'nested-tab', group: gettext('Parameters'),
|
||||
mode: ['edit', 'create'], deps: ['is_partitioned'],
|
||||
schema: this.vacuumSettingsSchema,
|
||||
},
|
||||
{
|
||||
id: 'relacl_str', label: gettext('Privileges'), disabled: this.inCatalog,
|
||||
type: 'text', mode: ['properties'], group: gettext('Security'),
|
||||
},
|
||||
{
|
||||
id: 'relacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: gettext('Security'), 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',
|
||||
},{
|
||||
id: 'vacuum_settings_str', label: gettext('Storage settings'),
|
||||
type: 'multiline', group: 'advanced', mode: ['properties'],
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
if (state.is_partitioned && this.isNew(state) &&
|
||||
(!state.partition_keys || state.partition_keys && state.partition_keys.length <= 0)) {
|
||||
setError('partition_keys', gettext('Please specify at least one key for partitioned table.'));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ export class VacuumTableSchema extends BaseUISchema {
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'label', name: 'label', label: gettext('Label'),
|
||||
id: 'label', name: 'label', label: gettext('Label'), cell: '',
|
||||
},
|
||||
{
|
||||
id: 'value', name: 'value', label: gettext('Value'),
|
||||
@ -40,7 +40,7 @@ export class VacuumTableSchema extends BaseUISchema {
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'setting', name: 'setting', label: gettext('Default'),
|
||||
id: 'setting', name: 'setting', label: gettext('Default'), cell: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import 'wcdocker';
|
||||
export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, formType, container, containerPanel, onCancel, onEdit, onSave) {
|
||||
let nodeObj = pgAdmin.Browser.Nodes[nodeType];
|
||||
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
|
||||
pgAdmin.Browser.serverInfo && pgAdmin.Browser[treeNodeInfo.server._id];
|
||||
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
|
||||
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
|
||||
let urlBase = generateNodeUrl.call(nodeObj, treeNodeInfo, actionType, itemNodeData, false, null);
|
||||
const api = getApiInstance();
|
||||
|
@ -322,7 +322,7 @@ export default function DataGridView({
|
||||
return props.columns.indexOf(firstF.id) < props.columns.indexOf(secondF.id) ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}).map((field, fieldIdx)=>{
|
||||
}).map((field)=>{
|
||||
let colInfo = {
|
||||
Header: field.label||<> </>,
|
||||
accessor: field.id,
|
||||
@ -333,7 +333,7 @@ export default function DataGridView({
|
||||
...(field.width ? {width: field.width} : {}),
|
||||
Cell: ({value, row, ...other}) => {
|
||||
/* Make sure to take the latest field info from schema */
|
||||
field = schemaRef.current.fields[fieldIdx];
|
||||
field = _.find(schemaRef.current.fields, (f)=>f.id==field.id) || field;
|
||||
|
||||
let {visible, editable, readonly, ..._field} = field;
|
||||
|
||||
@ -359,6 +359,10 @@ export default function DataGridView({
|
||||
editable = _.isUndefined(editable) ? true : editable;
|
||||
editable = evalFunc(schemaRef.current, editable, row.original || {});
|
||||
|
||||
if(_.isUndefined(_field.cell)) {
|
||||
console.error('cell is required ', _field);
|
||||
}
|
||||
|
||||
return <MappedCellControl rowIndex={row.index} value={value}
|
||||
row={row.original} {..._field}
|
||||
readonly={_readonly}
|
||||
@ -399,7 +403,7 @@ export default function DataGridView({
|
||||
path: accessPath,
|
||||
value: newRow,
|
||||
});
|
||||
}, []);
|
||||
}, [props.canAddRow]);
|
||||
|
||||
const defaultColumn = useMemo(()=>({
|
||||
minWidth: 175,
|
||||
|
@ -370,7 +370,7 @@ export default function FormView({
|
||||
<Box height="100%" display="flex" flexDirection="column" className={className} ref={formRef}>
|
||||
{Object.keys(finalTabs).map((tabName)=>{
|
||||
return (
|
||||
<>{finalTabs[tabName]}</>
|
||||
<React.Fragment key={tabName}>{finalTabs[tabName]}</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
@ -137,11 +137,19 @@ export default class BaseUISchema {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check */
|
||||
/* Check if node in catalog */
|
||||
inCatalog() {
|
||||
if(this.nodeInfo && 'catalog' in this.nodeInfo) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get the server version */
|
||||
getServerVersion() {
|
||||
if(!_.isUndefined(this.nodeInfo) && !_.isUndefined(this.nodeInfo.server)
|
||||
&& !_.isUndefined(this.nodeInfo.server.version)) {
|
||||
return this.nodeInfo.server.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -475,7 +475,7 @@ function SchemaDialogView({
|
||||
data = data || {};
|
||||
/* Set the origData to incoming data, useful for comparing and reset */
|
||||
schema.origData = prepareData(data || {});
|
||||
schema.initialise(data);
|
||||
schema.initialise(schema.origData);
|
||||
sessDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||
payload: schema.origData,
|
||||
@ -487,6 +487,7 @@ function SchemaDialogView({
|
||||
} else {
|
||||
/* Use the defaults as the initital data */
|
||||
schema.origData = prepareData(schema.defaults);
|
||||
schema.initialise(schema.origData);
|
||||
sessDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||
payload: schema.origData,
|
||||
@ -734,6 +735,7 @@ function SchemaPropertiesView({
|
||||
let defaultTab = 'General';
|
||||
let tabs = {};
|
||||
let tabsClassname = {};
|
||||
let groupLabels = {};
|
||||
const [origData, setOrigData] = useState({});
|
||||
const [loaderText, setLoaderText] = useState('');
|
||||
|
||||
@ -760,7 +762,9 @@ function SchemaPropertiesView({
|
||||
}
|
||||
|
||||
readonly = true;
|
||||
if(visible && modeSupported) {
|
||||
if(modeSupported) {
|
||||
group = groupLabels[group] || group || defaultTab;
|
||||
|
||||
if(!tabs[group]) tabs[group] = [];
|
||||
if(field && field.type === 'nested-fieldset') {
|
||||
tabs[group].push(
|
||||
@ -793,6 +797,11 @@ function SchemaPropertiesView({
|
||||
visible={visible}
|
||||
/>
|
||||
);
|
||||
} else if(field.type === 'group') {
|
||||
groupLabels[field.id] = field.label;
|
||||
if(!visible) {
|
||||
schema.filterGroups.push(field.label);
|
||||
}
|
||||
} else {
|
||||
tabs[group].push(
|
||||
<MappedFormControl
|
||||
@ -813,6 +822,7 @@ function SchemaPropertiesView({
|
||||
}
|
||||
});
|
||||
|
||||
let finalTabs = _.pickBy(tabs, (v, tabName)=>schema.filterGroups.indexOf(tabName) <= -1);
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
<Loader message={loaderText}/>
|
||||
@ -825,7 +835,7 @@ function SchemaPropertiesView({
|
||||
</Box>
|
||||
<Box className={classes.form}>
|
||||
<Box>
|
||||
{Object.keys(tabs).map((tabName)=>{
|
||||
{Object.keys(finalTabs).map((tabName)=>{
|
||||
let id = tabName.replace(' ', '');
|
||||
return (
|
||||
<Accordion key={id}>
|
||||
@ -838,7 +848,7 @@ function SchemaPropertiesView({
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box style={{width: '100%'}}>
|
||||
{tabs[tabName]}
|
||||
{finalTabs[tabName]}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
@ -665,7 +665,7 @@ function getRealValue(options, value, creatable, formatter) {
|
||||
}
|
||||
} else {
|
||||
realValue = _.find(options, (option)=>option.value==value) ||
|
||||
(creatable && !_.isUndefined(value) ? {label:value, value: value} : null);
|
||||
(creatable && !_.isUndefined(value) && !_.isNull(value) ? {label:value, value: value} : null);
|
||||
}
|
||||
return realValue;
|
||||
}
|
||||
@ -699,7 +699,8 @@ export function InputSelect({
|
||||
|
||||
/* Apply filter if any */
|
||||
const filteredOptions = (controlProps.filter && controlProps.filter(finalOptions)) || finalOptions;
|
||||
const realValue = getRealValue(filteredOptions, value, controlProps.creatable, controlProps.formatter);
|
||||
let realValue = getRealValue(filteredOptions, value, controlProps.creatable, controlProps.formatter);
|
||||
realValue = _.isNull(realValue) ? '' : realValue;
|
||||
const otherProps = {
|
||||
isSearchable: !readonly,
|
||||
isClearable: !readonly && (!_.isUndefined(controlProps.allowClear) ? controlProps.allowClear : true),
|
||||
@ -755,7 +756,7 @@ export function InputSelect({
|
||||
}
|
||||
InputSelect.propTypes = {
|
||||
cid: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.bool]),
|
||||
options: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func]),
|
||||
controlProps: PropTypes.object,
|
||||
optionsLoaded: PropTypes.func,
|
||||
|
@ -537,7 +537,7 @@ describe('FormComponents', ()=>{
|
||||
setTimeout(()=>{
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('accessibility', ()=>{
|
||||
@ -559,16 +559,6 @@ describe('FormComponents', ()=>{
|
||||
/>);
|
||||
});
|
||||
|
||||
// if(close) {
|
||||
// TheIcon = CloseIcon;
|
||||
// } else if(type === MESSAGE_TYPE.SUCCESS) {
|
||||
// TheIcon = CheckIcon;
|
||||
// } else if(type === MESSAGE_TYPE.ERROR) {
|
||||
// TheIcon = ReportProblemIcon;
|
||||
// } else if(type === MESSAGE_TYPE.INFO) {
|
||||
// TheIcon = InfoIcon;
|
||||
// }
|
||||
|
||||
it('init', ()=>{
|
||||
expect(ctrl.find(CheckIcon).exists()).toBeTrue();
|
||||
expect(ctrl.text()).toBe('Some message');
|
||||
|
@ -9,15 +9,15 @@
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
beforeAll(function () {
|
||||
spyOn(console, 'warn').and.callThrough();
|
||||
spyOn(console, 'error').and.callThrough();
|
||||
});
|
||||
// beforeAll(function () {
|
||||
// spyOn(console, 'warn').and.callThrough();
|
||||
// spyOn(console, 'error').and.callThrough();
|
||||
// });
|
||||
|
||||
afterEach(function (done) {
|
||||
setTimeout(function () {
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
// afterEach(function (done) {
|
||||
// setTimeout(function () {
|
||||
// expect(console.warn).not.toHaveBeenCalled();
|
||||
// expect(console.error).not.toHaveBeenCalled();
|
||||
// done();
|
||||
// }, 0);
|
||||
// });
|
||||
|
@ -0,0 +1,161 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import CheckConstraintSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint.ui';
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: new CheckConstraintSchema(),
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name', 'consrc'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('CheckConstraintSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new CheckConstraintSchema();
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl();
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {name: ''};
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'comment')(state)).toEqual({
|
||||
comment: '',
|
||||
});
|
||||
|
||||
/* If partitioned table */
|
||||
schemaObj.nodeInfo = {table: {}};
|
||||
schemaObj.top = {
|
||||
sessData: {
|
||||
is_partitioned: true,
|
||||
}
|
||||
};
|
||||
expect(getFieldDepChange(schemaObj, 'connoinherit')(state)).toEqual({
|
||||
connoinherit: false,
|
||||
});
|
||||
schemaObj.top = null;
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
expect(schemaObj.validate()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,6 @@ import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { messages } from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
// web/pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.ui.js
|
||||
import CollationSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.ui';
|
||||
|
||||
|
||||
|
307
web/regression/javascript/schema_ui_files/column.ui.spec.js
Normal file
307
web/regression/javascript/schema_ui_files/column.ui.spec.js
Normal file
@ -0,0 +1,307 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
import ColumnSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
class MockSchema extends BaseUISchema {
|
||||
get baseFields() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class ColumnInColl extends BaseUISchema {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'columns', label: '', type: 'collection',
|
||||
schema: new ColumnSchema(
|
||||
()=>new MockSchema(),
|
||||
{},
|
||||
()=>Promise.resolve([]),
|
||||
()=>Promise.resolve([]),
|
||||
),
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name' , 'cltype', 'attlen', 'attprecision', 'attnotnull', 'is_primary_key'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('ColumnSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new ColumnSchema(
|
||||
()=>new MockSchema(),
|
||||
{},
|
||||
()=>Promise.resolve([]),
|
||||
()=>Promise.resolve([]),
|
||||
);
|
||||
let datatypes = [
|
||||
{value: 'numeric', length: true, precision: true, min_val: 1, max_val: 140391},
|
||||
{value: 'character varying', length: true, precision: false, min_val: 1, max_val: 140391},
|
||||
];
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new ColumnInColl();
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('isTypeIdentity', ()=>{
|
||||
let state = {colconstype: 'i'};
|
||||
expect(schemaObj.isTypeIdentity(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('isTypeGenerated', ()=>{
|
||||
let state = {colconstype: 'g'};
|
||||
expect(schemaObj.isTypeGenerated(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('inSchemaWithModelCheck', ()=>{
|
||||
let state = {attnum: 1};
|
||||
schemaObj.nodeInfo.schema = {};
|
||||
expect(schemaObj.inSchemaWithModelCheck(state)).toBe(true);
|
||||
state.attnum = null;
|
||||
expect(schemaObj.inSchemaWithModelCheck(state)).toBe(false);
|
||||
schemaObj.nodeInfo = {};
|
||||
expect(schemaObj.inSchemaWithModelCheck(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('attlenRange', ()=>{
|
||||
schemaObj.datatypes = datatypes;
|
||||
let state = {cltype: 'character varying'};
|
||||
expect(schemaObj.attlenRange(state)).toEqual({min: 1, max: 140391});
|
||||
});
|
||||
|
||||
it('attprecisionRange', ()=>{
|
||||
schemaObj.datatypes = datatypes;
|
||||
let state = {cltype: 'numeric'};
|
||||
expect(schemaObj.attprecisionRange(state)).toEqual({min: 1, max: 140391});
|
||||
});
|
||||
|
||||
it('inSchemaWithColumnCheck', ()=>{
|
||||
let state = {};
|
||||
schemaObj.nodeInfo = {schema: {}};
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(false);
|
||||
|
||||
state.attnum = -1;
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(true);
|
||||
|
||||
state.inheritedfrom = 140391;
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(true);
|
||||
|
||||
schemaObj.nodeInfo.view = {};
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(true);
|
||||
|
||||
schemaObj.nodeInfo = {};
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(false);
|
||||
});
|
||||
|
||||
it('editableCheckForTable', ()=>{
|
||||
let state = {};
|
||||
schemaObj.nodeInfo = {};
|
||||
expect(schemaObj.editableCheckForTable(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
schemaObj.datatypes = datatypes;
|
||||
let state = {cltype: 'numeric'};
|
||||
getFieldDepChange(schemaObj, 'collspcname')(state);
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'attlen')(state)).toEqual({
|
||||
cltype: 'numeric',
|
||||
min_val_attlen: 1,
|
||||
max_val_attlen: 140391,
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'attprecision')(state)).toEqual({
|
||||
cltype: 'numeric',
|
||||
min_val_attprecision: 1,
|
||||
max_val_attprecision: 140391,
|
||||
});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.cltype = 'bigint';
|
||||
state.min_val_attlen = 5;
|
||||
state.max_val_attlen = 10;
|
||||
state.attlen = 3;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('attlen', 'Length/Precision should not be less than: 5');
|
||||
state.attlen = 11;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('attlen', 'Length/Precision should not be greater than: 10');
|
||||
|
||||
state.attlen = 6;
|
||||
state.min_val_attprecision = 5;
|
||||
state.max_val_attprecision = 10;
|
||||
state.attprecision = 3;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('attprecision', 'Scale should not be less than: 5');
|
||||
state.attprecision = 11;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('attprecision', 'Scale should not be greater than: 10');
|
||||
|
||||
state.attprecision = 6;
|
||||
state.colconstype = 'g';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('genexpr', 'Expression value cannot be empty.');
|
||||
|
||||
state.attnum = 1;
|
||||
state.attidentity = 'a';
|
||||
state.colconstype = 'i';
|
||||
schemaObj.origData = {attidentity:'a'};
|
||||
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqincrement', 'Increment value cannot be empty.');
|
||||
|
||||
state.seqincrement = 1;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqmin', 'Minimum value cannot be empty.');
|
||||
|
||||
state.seqmin = 1;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqmax', 'Maximum value cannot be empty.');
|
||||
|
||||
state.seqmax = 1;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqcache', 'Cache value cannot be empty.');
|
||||
|
||||
state.attnum = null;
|
||||
state.seqmin = null;
|
||||
state.seqmax = null;
|
||||
schemaObj.origData.attidentity = undefined;
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
|
||||
state.seqmin = 3;
|
||||
state.seqmax = 2;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqmin', 'Minimum value must be less than maximum value.');
|
||||
|
||||
state.seqmin = 3;
|
||||
state.seqmax = 5;
|
||||
state.seqstart = 2;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqstart', 'Start value cannot be less than minimum value.');
|
||||
|
||||
state.seqstart = 6;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('seqstart', 'Start value cannot be greater than maximum value.');
|
||||
|
||||
|
||||
state.seqstart = 4;
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,323 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import { getNodeExclusionConstraintSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.ui';
|
||||
import * as legacyConnector from 'sources/helpers/legacyConnector';
|
||||
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
|
||||
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor(schemaObj) {
|
||||
super();
|
||||
this.schemaObj = schemaObj;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: this.schemaObj,
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name', 'consrc'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('ExclusionConstraintSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue(Promise.resolve([]));
|
||||
spyOn(nodeAjax, 'getNodeListByName').and.returnValue(Promise.resolve([]));
|
||||
schemaObj = getNodeExclusionConstraintSchema({}, {}, {Nodes: {table: {}}});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl(schemaObj);
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('changeColumnOptions', ()=>{
|
||||
spyOn(schemaObj.exHeaderSchema, 'changeColumnOptions').and.callThrough();
|
||||
let columns = [{label: 'label', value: 'value'}];
|
||||
schemaObj.changeColumnOptions(columns);
|
||||
expect(schemaObj.exHeaderSchema.changeColumnOptions).toHaveBeenCalledWith(columns);
|
||||
});
|
||||
|
||||
describe('ExclusionColHeaderSchema', ()=>{
|
||||
it('getNewData', ()=>{
|
||||
schemaObj.exHeaderSchema.columnOptions = [
|
||||
{label: 'id', value: 'id', datatype: 'numeric'},
|
||||
{label: 'name', value: 'name', datatype: 'char'}
|
||||
];
|
||||
spyOn(schemaObj.exColumnSchema, 'getNewData');
|
||||
schemaObj.exHeaderSchema.getNewData({
|
||||
is_exp: false,
|
||||
column: 'id',
|
||||
expression: null,
|
||||
});
|
||||
expect(schemaObj.exColumnSchema.getNewData).toHaveBeenCalledWith({
|
||||
is_exp: false,
|
||||
column: 'id',
|
||||
col_type: 'numeric',
|
||||
});
|
||||
|
||||
schemaObj.exHeaderSchema.getNewData({
|
||||
is_exp: true,
|
||||
column: null,
|
||||
expression: 'abc',
|
||||
});
|
||||
expect(schemaObj.exColumnSchema.getNewData).toHaveBeenCalledWith({
|
||||
is_exp: true,
|
||||
column: 'abc',
|
||||
col_type: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExclusionColumnSchema', ()=>{
|
||||
it('isEditable', ()=>{
|
||||
schemaObj.exColumnSchema.isNewExCons = false;
|
||||
expect(schemaObj.exColumnSchema.isEditable()).toBe(false);
|
||||
|
||||
schemaObj.exColumnSchema.isNewExCons = true;
|
||||
schemaObj.exColumnSchema.amname = 'gist';
|
||||
expect(schemaObj.exColumnSchema.isEditable()).toBe(false);
|
||||
|
||||
schemaObj.exColumnSchema.amname = 'btree';
|
||||
expect(schemaObj.exColumnSchema.isEditable()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {columns: [{local_column: 'id'}]};
|
||||
|
||||
schemaObj.nodeInfo = {table: {}};
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], null, {
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns'],
|
||||
value: 0,
|
||||
})).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], {
|
||||
columns: [
|
||||
{name: 'id123'}
|
||||
],
|
||||
}, {
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns', 0, 'name'],
|
||||
value: 'id123',
|
||||
})).toEqual({
|
||||
columns: [{local_column: 'id123'}],
|
||||
});
|
||||
|
||||
state = {};
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({});
|
||||
state.index = 'idx';
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({include: []});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'comment')(state)).toEqual({
|
||||
comment: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('columns formatter', ()=>{
|
||||
let formatter = _.find(schemaObj.fields, (f)=>f.id=='columns').cell().controlProps.formatter;
|
||||
expect(formatter.fromRaw([{
|
||||
local_column: 'lid',
|
||||
referenced: 'rid',
|
||||
}])).toBe('(lid) -> (rid)');
|
||||
|
||||
expect(formatter.fromRaw([])).toBe('');
|
||||
});
|
||||
|
||||
describe('amname change', ()=>{
|
||||
let confirmSpy;
|
||||
let deferredDepChange;
|
||||
let operClassOptions = [
|
||||
{label: 'oper1', value: 'oper1'}
|
||||
];
|
||||
|
||||
beforeEach(()=>{
|
||||
spyOn(schemaObj.exColumnSchema, 'setOperClassOptions').and.callThrough();
|
||||
spyOn(schemaObj.fieldOptions, 'getOperClass').and.returnValue(operClassOptions);
|
||||
confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
|
||||
deferredDepChange = _.find(schemaObj.fields, (f)=>f.id=='amname')?.deferredDepChange;
|
||||
});
|
||||
|
||||
it('btree', (done)=>{
|
||||
let state = {amname: 'btree'};
|
||||
let deferredPromise = deferredDepChange(state);
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(schemaObj.exColumnSchema.setOperClassOptions).toHaveBeenCalledWith(operClassOptions);
|
||||
expect(depChange()).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
done();
|
||||
});
|
||||
/* Press OK */
|
||||
confirmSpy.calls.argsFor(0)[2]();
|
||||
});
|
||||
|
||||
it('not btree', (done)=>{
|
||||
let state = {amname: 'gist'};
|
||||
let deferredPromise = deferredDepChange(state);
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(schemaObj.exColumnSchema.setOperClassOptions).toHaveBeenCalledWith([]);
|
||||
expect(depChange()).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
done();
|
||||
});
|
||||
/* Press OK */
|
||||
confirmSpy.calls.argsFor(0)[2]();
|
||||
});
|
||||
|
||||
it('press no', (done)=>{
|
||||
let state = {amname: 'gist'};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
amname: 'btree',
|
||||
},
|
||||
});
|
||||
/* Press Cancel */
|
||||
confirmSpy.calls.argsFor(0)[3]();
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(depChange()).toEqual({
|
||||
amname: 'btree',
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.columns = ['id'];
|
||||
state.autoindex = true;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('coveringindex', 'Please specify covering index name.');
|
||||
|
||||
state.coveringindex = 'asdas';
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
266
web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js
Normal file
266
web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js
Normal file
@ -0,0 +1,266 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
|
||||
import { getNodeForeignKeySchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui';
|
||||
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor(schemaObj) {
|
||||
super();
|
||||
this.schemaObj = schemaObj;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: this.schemaObj,
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name', 'columns','references_table_name'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('ForeignKeySchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue(Promise.resolve([]));
|
||||
spyOn(nodeAjax, 'getNodeListByName').and.returnValue(Promise.resolve([]));
|
||||
schemaObj = getNodeForeignKeySchema({}, {}, {Nodes: {table: {}}});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl(schemaObj);
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('changeColumnOptions', ()=>{
|
||||
spyOn(schemaObj.fkHeaderSchema, 'changeColumnOptions').and.callThrough();
|
||||
let columns = [{label: 'label', value: 'value'}];
|
||||
schemaObj.changeColumnOptions(columns);
|
||||
expect(schemaObj.fkHeaderSchema.changeColumnOptions).toHaveBeenCalledWith(columns);
|
||||
});
|
||||
|
||||
describe('ForeignKeyHeaderSchema', ()=>{
|
||||
it('getNewData', ()=>{
|
||||
schemaObj.fkHeaderSchema.refTables = [
|
||||
{label: 'tab1', value: 140391},
|
||||
{label: 'tab2', value: 180191},
|
||||
];
|
||||
|
||||
expect(schemaObj.fkHeaderSchema.getNewData({
|
||||
local_column: 'lid',
|
||||
referenced: 'rid',
|
||||
references: 140391,
|
||||
})).toEqual({
|
||||
local_column: 'lid',
|
||||
referenced: 'rid',
|
||||
references: 140391,
|
||||
references_table_name: 'tab1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {columns: [{local_column: 'id'}]};
|
||||
let actionObj = {oldState:{name: 'fkname'}};
|
||||
|
||||
state.autoindex = true;
|
||||
state.name = 'fkname';
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({
|
||||
coveringindex: 'fki_fkname'
|
||||
});
|
||||
|
||||
state.name = 'fknamenew';
|
||||
state.coveringindex = 'fki_fkname';
|
||||
actionObj.oldState.name = 'fkname';
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({
|
||||
coveringindex: 'fki_fknamenew'
|
||||
});
|
||||
|
||||
state.autoindex = false;
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({
|
||||
coveringindex: ''
|
||||
});
|
||||
|
||||
state.hasindex = true;
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({});
|
||||
|
||||
state.oid = 140391;
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({});
|
||||
|
||||
state.oid = null;
|
||||
schemaObj.nodeInfo = {table: {}};
|
||||
expect(getFieldDepChange(schemaObj, 'autoindex')(state, null, null, actionObj)).toEqual({
|
||||
autoindex: false,
|
||||
coveringindex: '',
|
||||
});
|
||||
|
||||
state.name = '';
|
||||
expect(getFieldDepChange(schemaObj, 'comment')(state)).toEqual({
|
||||
comment: '',
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], null, {
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns'],
|
||||
value: 0,
|
||||
})).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], {
|
||||
columns: [
|
||||
{name: 'id123'}
|
||||
],
|
||||
}, {
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns', 0, 'name'],
|
||||
value: 'id123',
|
||||
})).toEqual({
|
||||
columns: [{local_column: 'id123'}],
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('columns formatter', ()=>{
|
||||
let formatter = _.find(schemaObj.fields, (f)=>f.id=='columns').cell().controlProps.formatter;
|
||||
expect(formatter.fromRaw([{
|
||||
local_column: 'lid',
|
||||
referenced: 'rid',
|
||||
}])).toBe('(lid) -> (rid)');
|
||||
|
||||
expect(formatter.fromRaw([])).toBe('');
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.columns = ['id'];
|
||||
state.autoindex = true;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('coveringindex', 'Please specify covering index name.');
|
||||
|
||||
state.coveringindex = 'asdas';
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,299 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
|
||||
import { PartitionKeysSchema, PartitionsSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.ui';
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor(schemaObj, columns) {
|
||||
super();
|
||||
this.collSchema = schemaObj;
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: this.collSchema,
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : this.columns,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
describe('PartitionKeysSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue(Promise.resolve([]));
|
||||
spyOn(nodeAjax, 'getNodeListByName').and.returnValue(Promise.resolve([]));
|
||||
schemaObj = new SchemaInColl(new PartitionKeysSchema());
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {};
|
||||
|
||||
state.key_type = 'expression';
|
||||
expect(getFieldDepChange(schemaObj.collSchema, 'pt_column')(state, [], null, {})).toEqual({
|
||||
pt_column: undefined,
|
||||
});
|
||||
|
||||
state.key_type = 'column';
|
||||
expect(getFieldDepChange(schemaObj.collSchema, 'expression')(state, [], null, {})).toEqual({
|
||||
expression: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.key_type = 'expression';
|
||||
schemaObj.collSchema.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('expression', '\'Partition key expression\' cannot be empty.');
|
||||
|
||||
state.expression = 'abc';
|
||||
expect(schemaObj.collSchema.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('PartitionsSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue(Promise.resolve([]));
|
||||
spyOn(nodeAjax, 'getNodeListByName').and.returnValue(Promise.resolve([]));
|
||||
schemaObj = new PartitionsSchema();
|
||||
schemaObj.top = schemaObj;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl(
|
||||
schemaObj,[ 'is_attach', 'partition_name', 'is_default', 'values_from', 'values_to', 'values_in', 'values_modulus', 'values_remainder']
|
||||
);
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {};
|
||||
|
||||
state.is_attach = true;
|
||||
expect(getFieldDepChange(schemaObj, 'is_sub_partitioned')(state)).toEqual({
|
||||
is_sub_partitioned: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {is_sub_partitioned: true};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('sub_partition_keys', 'Please specify at least one key for partitioned table.');
|
||||
|
||||
state.is_sub_partitioned = false;
|
||||
state.is_default = false;
|
||||
schemaObj.top._sessData = {
|
||||
partition_type: 'range',
|
||||
};
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('values_from', 'For range partition From field cannot be empty.');
|
||||
|
||||
state.values_from = 1;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('values_to', 'For range partition To field cannot be empty.');
|
||||
|
||||
schemaObj.top._sessData.partition_type = 'list';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('values_in', 'For list partition In field cannot be empty.');
|
||||
|
||||
schemaObj.top._sessData.partition_type = 'hash';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('values_modulus', 'For hash partition Modulus field cannot be empty.');
|
||||
|
||||
state.values_modulus = 1;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('values_remainder', 'For hash partition Remainder field cannot be empty.');
|
||||
});
|
||||
});
|
||||
|
217
web/regression/javascript/schema_ui_files/primary_key.ui.spec.js
Normal file
217
web/regression/javascript/schema_ui_files/primary_key.ui.spec.js
Normal file
@ -0,0 +1,217 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import PrimaryKeySchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.ui';
|
||||
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor(schemaObj) {
|
||||
super();
|
||||
this.schemaObj = schemaObj;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: this.schemaObj,
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name', 'columns'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('PrimaryKeySchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
schemaObj = new PrimaryKeySchema({
|
||||
spcname: ()=>Promise.resolve([]),
|
||||
}, {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl(schemaObj);
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {columns: [{column: 'id'}]};
|
||||
|
||||
state.name = '';
|
||||
expect(getFieldDepChange(schemaObj, 'comment')(state)).toEqual({
|
||||
comment: '',
|
||||
});
|
||||
|
||||
state.index = 'someindex';
|
||||
expect(getFieldDepChange(schemaObj, 'spcname')(state)).toEqual({
|
||||
spcname: '',
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({
|
||||
include: [],
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'fillfactor')(state)).toEqual({
|
||||
fillfactor: null,
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferrable')(state)).toEqual({
|
||||
condeferrable: false,
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferred')(state)).toEqual({
|
||||
condeferred: false,
|
||||
});
|
||||
|
||||
state.index = undefined;
|
||||
expect(getFieldDepChange(schemaObj, 'spcname')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'fillfactor')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferrable')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferred')(state)).toEqual({});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], null, {
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns'],
|
||||
value: 0,
|
||||
})).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], {
|
||||
columns: [
|
||||
{name: 'id123'}
|
||||
],
|
||||
}, {
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns', 0, 'name'],
|
||||
value: 'id123',
|
||||
})).toEqual({
|
||||
columns: [{column: 'id123'}],
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('columns', 'Please specify columns for Primary key.');
|
||||
|
||||
state.columns = [{columns: 'id'}];
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ describe('ServerSchema', ()=>{
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
|
@ -63,7 +63,7 @@ describe('ServerGroupSchema', ()=>{
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
|
359
web/regression/javascript/schema_ui_files/table.ui.spec.js
Normal file
359
web/regression/javascript/schema_ui_files/table.ui.spec.js
Normal file
@ -0,0 +1,359 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../../../pgadmin/static/js/SchemaView';
|
||||
import _ from 'lodash';
|
||||
import { getNodeTableSchema, LikeSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
|
||||
import * as legacyConnector from 'sources/helpers/legacyConnector';
|
||||
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('TableSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue(Promise.resolve([]));
|
||||
spyOn(nodeAjax, 'getNodeListByName').and.returnValue(Promise.resolve([]));
|
||||
schemaObj = getNodeTableSchema({
|
||||
server: {
|
||||
_id: 1,
|
||||
},
|
||||
schema: {
|
||||
_label: 'public',
|
||||
}
|
||||
}, {}, {
|
||||
Nodes: {table: {}},
|
||||
serverInfo: {
|
||||
1: {
|
||||
user: {
|
||||
name: 'Postgres',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'edit',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('getTableOid', ()=>{
|
||||
schemaObj.inheritedTableList = [
|
||||
{label: 'tab1', tid: 140391},
|
||||
{label: 'tab2', tid: 180191}
|
||||
];
|
||||
expect(schemaObj.getTableOid('tab2')).toBe(180191);
|
||||
});
|
||||
|
||||
it('canEditDeleteRowColumns', ()=>{
|
||||
expect(schemaObj.canEditDeleteRowColumns({inheritedfrom: 1234})).toBe(false);
|
||||
expect(schemaObj.canEditDeleteRowColumns({inheritedfrom: null})).toBe(true);
|
||||
});
|
||||
|
||||
it('LikeSchema typname change', ()=>{
|
||||
let likeSchemaObj = new LikeSchema([]);
|
||||
/* Dummy */
|
||||
likeSchemaObj.top = new LikeSchema([]);
|
||||
let depResp = getFieldDepChange(likeSchemaObj, 'like_relation')({typname: 'type1'}, ['typname']);
|
||||
expect(depResp).toEqual({
|
||||
like_relation: null,
|
||||
like_default_value: false,
|
||||
like_constraints: false,
|
||||
like_indexes: false,
|
||||
like_storage: false,
|
||||
like_comments: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('typname change', ()=>{
|
||||
let confirmSpy;
|
||||
let deferredDepChange;
|
||||
let oftypeColumns = [
|
||||
{name: 'id'}
|
||||
];
|
||||
beforeEach(()=>{
|
||||
spyOn(schemaObj,'changeColumnOptions').and.callThrough();
|
||||
spyOn(schemaObj, 'getTableOid').and.returnValue(140391);
|
||||
confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
|
||||
deferredDepChange = _.find(schemaObj.fields, (f)=>f.id=='typname')?.deferredDepChange;
|
||||
schemaObj.ofTypeTables = [
|
||||
{label: 'type1', oftype_columns: oftypeColumns}
|
||||
];
|
||||
});
|
||||
|
||||
it('initial selection with OK', (done)=>{
|
||||
let state = {typname: 'type1'};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
typname: null,
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(depChange()).toEqual({
|
||||
columns: oftypeColumns,
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(oftypeColumns);
|
||||
done();
|
||||
});
|
||||
/* Press OK */
|
||||
confirmSpy.calls.argsFor(0)[2]();
|
||||
});
|
||||
|
||||
it('initial selection with Cancel', (done)=>{
|
||||
let state = {typname: 'type1'};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
typname: null,
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(depChange()).toEqual({
|
||||
typname: null,
|
||||
});
|
||||
done();
|
||||
});
|
||||
/* Press Cancel */
|
||||
confirmSpy.calls.argsFor(0)[3]();
|
||||
});
|
||||
|
||||
it('later selection', (done)=>{
|
||||
let state = {typname: 'type1'};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
typname: 'typeold',
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(depChange()).toEqual({
|
||||
columns: oftypeColumns,
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(oftypeColumns);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('empty', (done)=>{
|
||||
let state = {typname: null};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
typname: null,
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
expect(depChange()).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('coll_inherits change', ()=>{
|
||||
let deferredDepChange;
|
||||
let inheritCol = {name: 'id'};
|
||||
|
||||
beforeEach(()=>{
|
||||
spyOn(schemaObj,'changeColumnOptions').and.callThrough();
|
||||
spyOn(schemaObj, 'getTableOid').and.returnValue(140391);
|
||||
spyOn(schemaObj, 'getColumns').and.returnValue(Promise.resolve([inheritCol]));
|
||||
deferredDepChange = _.find(schemaObj.fields, (f)=>f.id=='coll_inherits')?.deferredDepChange;
|
||||
});
|
||||
|
||||
it('add first selection', (done)=>{
|
||||
let state = {columns: [], coll_inherits: ['table1']};
|
||||
let newCol = schemaObj.columnsSchema.getNewData(inheritCol);
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
coll_inherits: [],
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
let finalCols = [newCol];
|
||||
expect(depChange(state)).toEqual({
|
||||
adding_inherit_cols: false,
|
||||
columns: finalCols,
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(finalCols);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('add more', (done)=>{
|
||||
let newCol = schemaObj.columnsSchema.getNewData(inheritCol);
|
||||
let state = {columns: [newCol], coll_inherits: ['table1', 'table2']};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
coll_inherits: ['table1'],
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
let finalCols = [newCol, newCol];
|
||||
expect(depChange(state)).toEqual({
|
||||
adding_inherit_cols: false,
|
||||
columns: [newCol, newCol],
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(finalCols);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('remove one table', (done)=>{
|
||||
inheritCol.inheritedid = 140391;
|
||||
let newCol = schemaObj.columnsSchema.getNewData(inheritCol);
|
||||
let state = {columns: [{name: 'desc'}, newCol], coll_inherits: ['table1']};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
coll_inherits: ['table1', 'table2'],
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
let finalCols = [{name: 'desc'}];
|
||||
expect(depChange(state)).toEqual({
|
||||
adding_inherit_cols: false,
|
||||
columns: finalCols,
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(finalCols);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('remove all', (done)=>{
|
||||
inheritCol.inheritedid = 140391;
|
||||
let newCol = schemaObj.columnsSchema.getNewData(inheritCol);
|
||||
let state = {columns: [{name: 'desc'}, newCol], coll_inherits: []};
|
||||
let deferredPromise = deferredDepChange(state, null, null, {
|
||||
oldState: {
|
||||
coll_inherits: ['table1'],
|
||||
},
|
||||
});
|
||||
deferredPromise.then((depChange)=>{
|
||||
let finalCols = [{name: 'desc'}];
|
||||
expect(depChange(state)).toEqual({
|
||||
adding_inherit_cols: false,
|
||||
columns: finalCols,
|
||||
});
|
||||
expect(schemaObj.changeColumnOptions).toHaveBeenCalledWith(finalCols);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
spyOn(schemaObj, 'getTableOid').and.returnValue(140391);
|
||||
let state = {};
|
||||
|
||||
state.is_partitioned = true;
|
||||
state.coll_inherits = ['table1'];
|
||||
expect(getFieldDepChange(schemaObj, 'coll_inherits')(state, ['is_partitioned'], null, {
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
path: ['is_partitioned'],
|
||||
})).toEqual({
|
||||
coll_inherits: [],
|
||||
});
|
||||
|
||||
spyOn(schemaObj, 'getServerVersion').and.returnValue(100000);
|
||||
schemaObj.constraintsObj.top = schemaObj;
|
||||
expect(getFieldDepChange(schemaObj.constraintsObj, 'primary_key')({is_partitioned: true})).toEqual({
|
||||
primary_key: []
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj.constraintsObj, 'foreign_key')({is_partitioned: true})).toEqual({
|
||||
foreign_key: []
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj.constraintsObj, 'unique_constraint')({is_partitioned: true})).toEqual({
|
||||
unique_constraint: []
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj.constraintsObj, 'exclude_constraint')({is_partitioned: true})).toEqual({
|
||||
exclude_constraint: []
|
||||
});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {is_partitioned: true};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('partition_keys', 'Please specify at least one key for partitioned table.');
|
||||
|
||||
state.partition_keys = [{key: 'id'}];
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -106,62 +106,5 @@ describe('TablespaceSchema', ()=>{
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
// it('validate', ()=>{
|
||||
// let state = {};
|
||||
// let setError = jasmine.createSpy('setError');
|
||||
//
|
||||
// state.seqowner = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('seqowner', '\'Owner\' cannot be empty.');
|
||||
//
|
||||
// state.seqowner = 'postgres';
|
||||
// state.schema = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('schema', '\'Schema\' cannot be empty.');
|
||||
//
|
||||
// state.schema = 'public';
|
||||
// state.oid = 12345;
|
||||
// state.current_value = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('current_value', '\'Current value\' cannot be empty.');
|
||||
//
|
||||
// state.current_value = 10;
|
||||
// state.increment = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('increment', '\'Increment value\' cannot be empty.');
|
||||
//
|
||||
//
|
||||
// state.increment = 1;
|
||||
// state.minimum = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('minimum', '\'Minimum value\' cannot be empty.');
|
||||
//
|
||||
// state.minimum = 5;
|
||||
// state.maximum = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('maximum', '\'Maximum value\' cannot be empty.');
|
||||
//
|
||||
// state.maximum = 200;
|
||||
// state.cache = null;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('cache', '\'Cache value\' cannot be empty.');
|
||||
//
|
||||
// state.cache = 1;
|
||||
// state.minimum = 10;
|
||||
// state.maximum = 5;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('minimum', 'Minimum value must be less than maximum value.');
|
||||
//
|
||||
// state.start = 5;
|
||||
// state.minimum = 10;
|
||||
// state.maximum = 50;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('start', 'Start value cannot be less than minimum value.');
|
||||
//
|
||||
// state.start = 500;
|
||||
// schemaObj.validate(state, setError);
|
||||
// expect(setError).toHaveBeenCalledWith('start', 'Start value cannot be greater than maximum value.');
|
||||
// });
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,216 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../../../pgadmin/static/js/SchemaView';
|
||||
import BaseUISchema from '../../../pgadmin/static/js/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
import UniqueConstraintSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.ui';
|
||||
|
||||
class SchemaInColl extends BaseUISchema {
|
||||
constructor(schemaObj) {
|
||||
super();
|
||||
this.schemaObj = schemaObj;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'collection', label: '', type: 'collection',
|
||||
schema: this.schemaObj,
|
||||
editable: false,
|
||||
canAdd: true, canEdit: false, canDelete: true, hasRole: true,
|
||||
columns : ['name', 'columns'],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function getFieldDepChange(schema, id) {
|
||||
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
|
||||
}
|
||||
|
||||
describe('UniqueConstraintSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj;
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
schemaObj = new UniqueConstraintSchema({
|
||||
spcname: ()=>Promise.resolve([]),
|
||||
}, {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('create collection', ()=>{
|
||||
let schemaCollObj = new SchemaInColl(schemaObj);
|
||||
let ctrl = mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaCollObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
disableDialogHelp={false}
|
||||
/>);
|
||||
/* Make sure you hit every corner */
|
||||
ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
let state = {columns: [{column: 'id'}]};
|
||||
state.name = '';
|
||||
expect(getFieldDepChange(schemaObj, 'comment')(state)).toEqual({
|
||||
comment: '',
|
||||
});
|
||||
|
||||
state.index = 'someindex';
|
||||
expect(getFieldDepChange(schemaObj, 'spcname')(state)).toEqual({
|
||||
spcname: '',
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({
|
||||
include: [],
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'fillfactor')(state)).toEqual({
|
||||
fillfactor: null,
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferrable')(state)).toEqual({
|
||||
condeferrable: false,
|
||||
});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferred')(state)).toEqual({
|
||||
condeferred: false,
|
||||
});
|
||||
|
||||
state.index = undefined;
|
||||
expect(getFieldDepChange(schemaObj, 'spcname')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'include')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'fillfactor')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferrable')(state)).toEqual({});
|
||||
expect(getFieldDepChange(schemaObj, 'condeferred')(state)).toEqual({});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], null, {
|
||||
type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns'],
|
||||
value: 0,
|
||||
})).toEqual({
|
||||
columns: [],
|
||||
});
|
||||
|
||||
expect(getFieldDepChange(schemaObj, 'columns')(state, ['columns', 0], {
|
||||
columns: [
|
||||
{name: 'id123'}
|
||||
],
|
||||
}, {
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
oldState: {
|
||||
columns: [
|
||||
{name: 'id'}
|
||||
],
|
||||
},
|
||||
path: ['columns', 0, 'name'],
|
||||
value: 'id123',
|
||||
})).toEqual({
|
||||
columns: [{column: 'id123'}],
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('columns', 'Please specify columns for Unique constraint.');
|
||||
|
||||
state.columns = [{columns: 'id'}];
|
||||
expect(schemaObj.validate(state, setError)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user