mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Port the partitions node.
This commit is contained in:
committed by
Akshay Joshi
parent
af27af8d42
commit
74cec6594e
@@ -7,6 +7,8 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodePartitionTableSchema } from './partition.ui';
|
||||
|
||||
define([
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
@@ -314,36 +316,14 @@ function(
|
||||
);
|
||||
},
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodePartitionTableSchema(treeNodeInfo, itemNodeData, pgBrowser);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
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,
|
||||
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: [],
|
||||
partitions: [],
|
||||
partition_type: 'range',
|
||||
is_partitioned: false,
|
||||
partition_value: undefined,
|
||||
},
|
||||
@@ -364,851 +344,22 @@ function(
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], disabled: 'inSchema',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'relowner', label: gettext('Owner'), type: 'text', node: 'role',
|
||||
mode: ['properties', 'create', 'edit'], select2: {allowClear: false},
|
||||
disabled: 'inSchema', control: 'node-list-by-name',
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), type: 'text', node: 'schema',
|
||||
control: 'node-list-by-name', mode: ['create', 'edit', 'properties'],
|
||||
disabled: 'inSchema', filter: function(d) {
|
||||
// If schema name start with pg_* then we need to exclude them
|
||||
if(d && d.label.match(/^pg_/))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, cache_node: 'database', cache_level: 'database',
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'), node: 'tablespace',
|
||||
type: 'text', control: 'node-list-by-name', disabled: 'inSchema',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
filter: function(d) {
|
||||
// If tablespace name is not "pg_global" then we need to exclude them
|
||||
return (!(d && d.label.match(/pg_global/)));
|
||||
},
|
||||
},{
|
||||
id: 'partition', type: 'group', label: gettext('Partition'),
|
||||
mode: ['edit', 'create'], min_version: 100000,
|
||||
visible: function(m) {
|
||||
// Always show in case of create mode
|
||||
if (m.isNew() || m.get('is_partitioned'))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
mode: ['create', 'edit', 'properties'],
|
||||
},{
|
||||
id: 'is_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
readonly: function(m) {
|
||||
if (!m.isNew())
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'], disabled: 'inSchema',
|
||||
},
|
||||
{
|
||||
id: 'partition_value', label:gettext('Partition Scheme'),
|
||||
type: 'text', visible: false,
|
||||
},{
|
||||
id: 'coll_inherits', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', group: gettext('Advanced'), mode: ['properties'],
|
||||
},{
|
||||
id: 'Columns', type: 'group', label: gettext('Columns'),
|
||||
mode: ['edit', 'create'], min_version: 100000,
|
||||
// Always hide in case of partition table.
|
||||
visible: function() { return false; },
|
||||
},{
|
||||
// Tab control for columns
|
||||
id: 'columns', label: gettext('Columns'), type: 'collection',
|
||||
group: gettext('Columns'),
|
||||
model: pgBrowser.Nodes['column'].model,
|
||||
subnode: pgBrowser.Nodes['column'].model,
|
||||
mode: ['create', 'edit'],
|
||||
disabled: function(m) {
|
||||
// In case of partitioned table remove inherited columns
|
||||
if (m.isNew() && m.get('is_partitioned')) {
|
||||
setTimeout(function() {
|
||||
var coll = m.get('columns');
|
||||
coll.remove(coll.filter(function(model) {
|
||||
if (_.isUndefined(model.get('inheritedfrom')))
|
||||
return false;
|
||||
return true;
|
||||
}));
|
||||
}, 10);
|
||||
}
|
||||
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
deps: ['typname', 'is_partitioned'],
|
||||
canAdd: 'check_grid_add_condition',
|
||||
canEdit: true, canDelete: true,
|
||||
// For each row edit/delete button enable/disable
|
||||
canEditRow: 'check_grid_row_edit_delete',
|
||||
canDeleteRow: 'check_grid_row_edit_delete',
|
||||
uniqueCol : ['name'],
|
||||
columns : ['name' , 'cltype', 'attlen', 'attprecision', 'attnotnull', 'is_primary_key'],
|
||||
control: Backform.UniqueColCollectionControl.extend({
|
||||
initialize: function() {
|
||||
Backform.UniqueColCollectionControl.prototype.initialize.apply(this, arguments);
|
||||
var self = this,
|
||||
collection = self.model.get(self.field.get('name'));
|
||||
|
||||
collection.on('change:is_primary_key', function(local_model) {
|
||||
var primary_key_coll = self.model.get('primary_key'),
|
||||
column_name = local_model.get('name'),
|
||||
primary_key, primary_key_column_coll;
|
||||
|
||||
if(local_model.get('is_primary_key')) {
|
||||
// Add column to primary key.
|
||||
if (primary_key_coll.length < 1) {
|
||||
primary_key = new (primary_key_coll.model)({}, {
|
||||
top: self.model,
|
||||
collection: primary_key_coll,
|
||||
handler: primary_key_coll,
|
||||
});
|
||||
primary_key_coll.add(primary_key);
|
||||
} else {
|
||||
primary_key = primary_key_coll.first();
|
||||
}
|
||||
// Do not alter existing primary key columns.
|
||||
if (_.isUndefined(primary_key.get('oid'))) {
|
||||
primary_key_column_coll = primary_key.get('columns');
|
||||
var primary_key_column_exist = primary_key_column_coll.where({column:column_name});
|
||||
|
||||
if (primary_key_column_exist.length == 0) {
|
||||
var primary_key_column = new (primary_key_column_coll.model)(
|
||||
{column: column_name}, { silent: true,
|
||||
top: self.model,
|
||||
collection: primary_key_coll,
|
||||
handler: primary_key_coll,
|
||||
});
|
||||
|
||||
primary_key_column_coll.add(primary_key_column);
|
||||
}
|
||||
|
||||
primary_key_column_coll.trigger(
|
||||
'pgadmin:multicolumn:updated', primary_key_column_coll
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// remove column from primary key.
|
||||
if (primary_key_coll.length > 0) {
|
||||
primary_key = primary_key_coll.first();
|
||||
// Do not alter existing primary key columns.
|
||||
if (!_.isUndefined(primary_key.get('oid'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
primary_key_column_coll = primary_key.get('columns');
|
||||
var removedCols = primary_key_column_coll.where({
|
||||
column: column_name,
|
||||
});
|
||||
if (removedCols.length > 0) {
|
||||
primary_key_column_coll.remove(removedCols);
|
||||
_.each(removedCols, function(m) {
|
||||
m.destroy();
|
||||
});
|
||||
if (primary_key_column_coll.length == 0) {
|
||||
setTimeout(function () {
|
||||
// There will be only on primary key so remove the first one.
|
||||
primary_key_coll.remove(primary_key_coll.first());
|
||||
/* Ideally above line of code should be "primary_key_coll.reset()".
|
||||
* But our custom DataCollection (extended from Backbone collection in datamodel.js)
|
||||
* does not respond to reset event, it only supports add, remove, change events.
|
||||
* And hence no custom event listeners/validators get called for reset event.
|
||||
*/
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
remove: function() {
|
||||
var collection = this.model.get(this.field.get('name'));
|
||||
if (collection) {
|
||||
collection.off('change:is_primary_key');
|
||||
}
|
||||
|
||||
Backform.UniqueColCollectionControl.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
allowMultipleEmptyRow: false,
|
||||
},{
|
||||
id: 'inherited_tables_cnt', label: gettext('Inherited tables count'),
|
||||
type: 'text', mode: ['properties'], group: gettext('Advanced'),
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
// Here we will create tab control for constraints
|
||||
type: 'nested', control: 'tab', group: gettext('Constraints'),
|
||||
mode: ['edit', 'create'],
|
||||
schema: [{
|
||||
id: 'primary_key', label: gettext('Primary key'),
|
||||
model: pgBrowser.Nodes['primary_key'].model,
|
||||
subnode: pgBrowser.Nodes['primary_key'].model,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Primary Key'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
control: 'unique-col-collection',
|
||||
columns : ['name', 'columns'],
|
||||
canAdd: function(m) {
|
||||
if (m.get('is_partitioned') && !_.isUndefined(m.top.node_info) && !_.isUndefined(m.top.node_info.server)
|
||||
&& !_.isUndefined(m.top.node_info.server.version) &&
|
||||
m.top.node_info.server.version < 110000) {
|
||||
setTimeout(function() {
|
||||
var coll = m.get('primary_key');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
canAddRow: function(m) {
|
||||
// User can only add one primary key
|
||||
var columns = m.get('columns');
|
||||
|
||||
return (m.get('primary_key') &&
|
||||
m.get('primary_key').length < 1 &&
|
||||
_.some(columns.pluck('name')));
|
||||
},
|
||||
},{
|
||||
id: 'foreign_key', label: gettext('Foreign key'),
|
||||
model: pgBrowser.Nodes['foreign_key'].model,
|
||||
subnode: pgBrowser.Nodes['foreign_key'].model,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Foreign Key'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
control: 'unique-col-collection',
|
||||
canAdd: function(m) {
|
||||
if (m.get('is_partitioned')) {
|
||||
setTimeout(function() {
|
||||
var coll = m.get('foreign_key');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
columns : ['name', 'columns'],
|
||||
canAddRow: function(m) {
|
||||
// User can only add if there is at least one column with name.
|
||||
var columns = m.get('columns');
|
||||
return _.some(columns.pluck('name'));
|
||||
},
|
||||
},{
|
||||
id: 'check_constraint', label: gettext('Check constraint'),
|
||||
model: pgBrowser.Nodes['check_constraint'].model,
|
||||
subnode: pgBrowser.Nodes['check_constraint'].model,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Check'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
control: 'unique-col-collection',
|
||||
canAdd: true,
|
||||
columns : ['name', 'consrc'],
|
||||
},{
|
||||
id: 'unique_constraint', label: gettext('Unique Constraint'),
|
||||
model: pgBrowser.Nodes['unique_constraint'].model,
|
||||
subnode: pgBrowser.Nodes['unique_constraint'].model,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Unique'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
control: 'unique-col-collection',
|
||||
columns : ['name', 'columns'],
|
||||
canAdd: function(m) {
|
||||
if (m.get('is_partitioned')) {
|
||||
setTimeout(function() {
|
||||
var coll = m.get('unique_constraint');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
canAddRow: function(m) {
|
||||
// User can only add if there is at least one column with name.
|
||||
var columns = m.get('columns');
|
||||
return _.some(columns.pluck('name'));
|
||||
},
|
||||
},{
|
||||
id: 'exclude_constraint', label: gettext('Exclude constraint'),
|
||||
model: pgBrowser.Nodes['exclusion_constraint'].model,
|
||||
subnode: pgBrowser.Nodes['exclusion_constraint'].model,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Exclude'), mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
control: 'unique-col-collection',
|
||||
columns : ['name', 'columns', 'constraint'],
|
||||
canAdd: function(m) {
|
||||
if (m.get('is_partitioned')) {
|
||||
setTimeout(function() {
|
||||
var coll = m.get('exclude_constraint');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
canAddRow: function(m) {
|
||||
// User can only add if there is at least one column with name.
|
||||
var columns = m.get('columns');
|
||||
return _.some(columns.pluck('name'));
|
||||
},
|
||||
}],
|
||||
},{
|
||||
id: 'typname', label: gettext('Of type'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Advanced'),
|
||||
disabled: 'checkOfType', url: 'get_oftype',
|
||||
deps: ['coll_inherits', 'is_partitioned'],
|
||||
transform: function(data, cell) {
|
||||
var control = cell || this,
|
||||
m = control.model;
|
||||
m.of_types_tables = data;
|
||||
return data;
|
||||
},
|
||||
control: Backform.NodeAjaxOptionsControl.extend({
|
||||
// When of_types changes we need to clear columns collection
|
||||
onChange: function() {
|
||||
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
|
||||
var self = this,
|
||||
tbl_name = self.model.get('typname'),
|
||||
data = undefined,
|
||||
arg = undefined,
|
||||
column_collection = self.model.get('columns');
|
||||
|
||||
if (!_.isUndefined(tbl_name) &&
|
||||
tbl_name !== '' && column_collection.length !== 0) {
|
||||
var msg = gettext('Changing of table type will clear columns collection.');
|
||||
Alertify.confirm(msg, function (e) {
|
||||
if (e) {
|
||||
// User clicks Ok, lets clear columns collection
|
||||
column_collection.reset();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
} else if (!_.isUndefined(tbl_name) && tbl_name === '') {
|
||||
column_collection.reset();
|
||||
}
|
||||
|
||||
// Run Ajax now to fetch columns
|
||||
if (!_.isUndefined(tbl_name) && tbl_name !== '') {
|
||||
arg = { 'tname': tbl_name };
|
||||
data = self.model.fetch_columns_ajax.apply(self, [arg]);
|
||||
// Add into column collection
|
||||
column_collection.set(data, { merge:false,remove:false });
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), type: 'int',
|
||||
mode: ['create', 'edit'], min: 10, max: 100,
|
||||
group: gettext('Advanced'),
|
||||
disabled: function(m) {
|
||||
if(m.get('is_partitioned')) {
|
||||
return true;
|
||||
}
|
||||
return m.inSchema();
|
||||
},
|
||||
},{
|
||||
id: 'relhasoids', label: gettext('Has OIDs?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
disabled: true, group: gettext('Advanced'),
|
||||
},{
|
||||
id: 'relpersistence', label: gettext('Unlogged?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchemaWithModelCheck',
|
||||
group: gettext('Advanced'),
|
||||
},{
|
||||
id: 'conname', label: gettext('Primary key'), cell: 'string',
|
||||
type: 'text', mode: ['properties'], group: gettext('Advanced'),
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
id: 'reltuples', label: gettext('Rows (estimated)'), cell: 'string',
|
||||
type: 'text', mode: ['properties'], group: gettext('Advanced'),
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
id: 'rows_cnt', label: gettext('Rows (counted)'), cell: 'string',
|
||||
type: 'text', mode: ['properties'], group: gettext('Advanced'),
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
id: 'relhassubclass', label: gettext('Inherits tables?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties'], group: gettext('Advanced'),
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
id: 'is_sys_table', label: gettext('System table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties'],
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: gettext('Like'),
|
||||
group: gettext('Advanced'),
|
||||
schema:[{
|
||||
id: 'like_relation', label: gettext('Relation'),
|
||||
type: 'text', mode: ['create', 'edit'], deps: ['typname'],
|
||||
control: 'node-ajax-options', url: 'get_relations',
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
},{
|
||||
id: 'like_default_value', label: gettext('With default values?'),
|
||||
type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
},{
|
||||
id: 'like_constraints', label: gettext('With constraints?'),
|
||||
type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
},{
|
||||
id: 'like_indexes', label: gettext('With indexes?'),
|
||||
type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
},{
|
||||
id: 'like_storage', label: gettext('With storage?'),
|
||||
type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
},{
|
||||
id: 'like_comments', label: gettext('With comments?'),
|
||||
type: 'switch', mode: ['create', 'edit'], deps: ['typname'],
|
||||
disabled: 'isLikeDisable', group: gettext('Like'),
|
||||
}],
|
||||
},{
|
||||
id: 'partition_type', label:gettext('Partition Type'),
|
||||
editable: false, type: 'select2', select2: {allowClear: false},
|
||||
group: 'partition', deps: ['is_partitioned'],
|
||||
options:[{
|
||||
label: gettext('Range'), value: 'range',
|
||||
},{
|
||||
label: gettext('List'), value: 'list',
|
||||
}],
|
||||
mode:['create'],
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
readonly: function(m) {
|
||||
return !m.isNew();
|
||||
},
|
||||
disabled: function(m) {
|
||||
return !m.get('is_partitioned');
|
||||
},
|
||||
},{
|
||||
id: 'partition_keys', label:gettext('Partition Keys'),
|
||||
model: Backform.PartitionKeyModel,
|
||||
subnode: Backform.PartitionKeyModel,
|
||||
editable: true, type: 'collection',
|
||||
group: 'partition', mode: ['create'],
|
||||
deps: ['is_partitioned', 'partition_type'],
|
||||
canEdit: false, canDelete: true,
|
||||
control: 'sub-node-collection',
|
||||
canAdd: function(m) {
|
||||
if (m.isNew() && m.get('is_partitioned'))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
canAddRow: function(m) {
|
||||
var columns = m.get('columns');
|
||||
var max_row_count = 1000;
|
||||
|
||||
if (m.get('partition_type') && m.get('partition_type') == 'list')
|
||||
max_row_count = 1;
|
||||
|
||||
return (m.get('partition_keys') &&
|
||||
m.get('partition_keys').length < max_row_count &&
|
||||
_.some(columns.pluck('name'))
|
||||
);
|
||||
},
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
disabled: function(m) {
|
||||
if (m.get('partition_keys') && m.get('partition_keys').models.length > 0) {
|
||||
setTimeout(function () {
|
||||
var coll = m.get('partition_keys');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'partition_scheme', label: gettext('Partition Scheme'),
|
||||
type: 'note', group: 'partition', mode: ['edit'],
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
disabled: function(m) {
|
||||
if (!m.isNew()) {
|
||||
this.text = m.get('partition_scheme');
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'partitions', label:gettext('Partitions'),
|
||||
model: Backform.PartitionsModel,
|
||||
subnode: Backform.PartitionsModel,
|
||||
editable: true, type: 'collection',
|
||||
group: 'partition', mode: ['edit', 'create'],
|
||||
deps: ['is_partitioned', 'partition_type'],
|
||||
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'],
|
||||
control: Backform.SubNodeCollectionControl.extend({
|
||||
row: Backgrid.PartitionRow,
|
||||
initialize: function() {
|
||||
Backform.SubNodeCollectionControl.prototype.initialize.apply(this, arguments);
|
||||
var self = this;
|
||||
if (!this.model.isNew()) {
|
||||
var node = this.field.get('schema_node'),
|
||||
node_info = this.field.get('node_info');
|
||||
|
||||
// Make ajax call to get the tables to be attached
|
||||
$.ajax({
|
||||
url: node.generate_url.apply(
|
||||
node, [
|
||||
null, 'get_attach_tables', this.field.get('node_data'),
|
||||
true, node_info,
|
||||
]),
|
||||
|
||||
type: 'GET',
|
||||
async: false,
|
||||
})
|
||||
.done(function(res) {
|
||||
if (res.success == 1) {
|
||||
self.model.table_options = res.data;
|
||||
}
|
||||
else {
|
||||
Alertify.alert(
|
||||
gettext('Error fetching tables to be attached'), res.data.result
|
||||
);
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
Alertify.pgRespErrorNotify(xhr, error, gettext('Error fetching tables to be attached'));
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
canAdd: function(m) {
|
||||
if (m.get('is_partitioned'))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
disabled: function(m) {
|
||||
if (
|
||||
m.isNew() && m.get('partitions') &&
|
||||
m.get('partitions').models.length > 0
|
||||
) {
|
||||
setTimeout(function () {
|
||||
var coll = m.get('partitions');
|
||||
coll.remove(coll.filter(function() { return true; }));
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'partition_note', label: gettext('Partition'),
|
||||
type: 'note', group: 'partition',
|
||||
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(''),
|
||||
visible: function(m) {
|
||||
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 100000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
// Here - we will create tab control for storage parameters
|
||||
// (auto vacuum).
|
||||
type: 'nested', control: 'tab', group: gettext('Parameter'),
|
||||
mode: ['edit', 'create'], deps: ['is_partitioned'],
|
||||
schema: Backform.VacuumSettingsSchema,
|
||||
},{
|
||||
id: 'relacl_str', label: gettext('Privileges'), disabled: 'inSchema',
|
||||
type: 'text', mode: ['properties'], group: gettext('Security'),
|
||||
}, pgBrowser.SecurityGroupSchema,{
|
||||
id: 'relacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: 'security', control: 'unique-col-collection',
|
||||
model: pgBrowser.Node.PrivilegeRoleModel.extend({
|
||||
privileges: ['a','r','w','d','D','x','t']}),
|
||||
mode: ['edit', 'create'], canAdd: true, canDelete: true,
|
||||
uniqueCol : ['grantee'],
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'), canEdit: false,
|
||||
model: pgBrowser.SecLabelModel, editable: false, canAdd: true,
|
||||
type: 'collection', min_version: 90100, mode: ['edit', 'create'],
|
||||
group: 'security', canDelete: true, control: 'unique-col-collection',
|
||||
},{
|
||||
id: 'vacuum_settings_str', label: gettext('Storage settings'),
|
||||
type: 'multiline', group: gettext('Advanced'), mode: ['properties'],
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
sessChanged: function() {
|
||||
/* If only custom autovacuum option is enabled then check if the options table is also changed. */
|
||||
if(_.size(this.sessAttrs) == 2 && this.sessAttrs['autovacuum_custom'] && this.sessAttrs['toast_autovacuum']) {
|
||||
return this.get('vacuum_table').sessChanged() || this.get('vacuum_toast').sessChanged();
|
||||
}
|
||||
if(_.size(this.sessAttrs) == 1 && (this.sessAttrs['autovacuum_custom'] || this.sessAttrs['toast_autovacuum'])) {
|
||||
return this.get('vacuum_table').sessChanged() || this.get('vacuum_toast').sessChanged();
|
||||
}
|
||||
return pgBrowser.DataModel.prototype.sessChanged.apply(this);
|
||||
},
|
||||
validate: function(keys) {
|
||||
var msg,
|
||||
name = this.get('name'),
|
||||
schema = this.get('schema'),
|
||||
relowner = this.get('relowner'),
|
||||
is_partitioned = this.get('is_partitioned'),
|
||||
partition_keys = this.get('partition_keys');
|
||||
|
||||
// If nothing to validate or VacuumSetting keys then
|
||||
// return from here
|
||||
if ( keys && (keys.length == 0
|
||||
|| _.indexOf(keys, 'autovacuum_enabled') != -1
|
||||
|| _.indexOf(keys, 'toast_autovacuum_enabled') != -1) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Have to clear existing validation before initiating current state validation only
|
||||
this.errorModel.clear();
|
||||
|
||||
if (_.isUndefined(name) || _.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Table name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
} else if (_.isUndefined(schema) || _.isNull(schema) ||
|
||||
String(schema).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Table schema cannot be empty.');
|
||||
this.errorModel.set('schema', msg);
|
||||
return msg;
|
||||
} else if (_.isUndefined(relowner) || _.isNull(relowner) ||
|
||||
String(relowner).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Table owner cannot be empty.');
|
||||
this.errorModel.set('relowner', msg);
|
||||
return msg;
|
||||
} else if (is_partitioned && this.isNew() &&
|
||||
!_.isNull(partition_keys) && partition_keys.length <= 0)
|
||||
{
|
||||
msg = gettext('Please specify at least one key for partitioned table.');
|
||||
this.errorModel.set('partition_keys', msg);
|
||||
return msg;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// We will disable everything if we are under catalog node
|
||||
inSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isInheritedTable: function(m) {
|
||||
if(!m.inSchema.apply(this, [m])) {
|
||||
// Either of_types or coll_inherits has value
|
||||
return (
|
||||
(_.isUndefined(m.get('coll_inherits')) || m.get('coll_inherits').length == 0)
|
||||
&&
|
||||
(_.isUndefined(m.get('typname')) || String(m.get('typname')).replace(/^\s+|\s+$/g, '') === '')
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// Oftype is defined?
|
||||
checkInheritance: function(m) {
|
||||
// Disabled if it is partitioned table
|
||||
if (m.get('is_partitioned')) {
|
||||
setTimeout( function() {
|
||||
m.set('coll_inherits', []);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
// coll_inherits || typname
|
||||
if(!m.inSchema.apply(this, [m]) &&
|
||||
( _.isUndefined(m.get('typname')) ||
|
||||
_.isNull(m.get('typname')) ||
|
||||
String(m.get('typname')).replace(/^\s+|\s+$/g, '') == '')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// We will disable Like if ofType is defined
|
||||
isLikeDisable: function(m) {
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
|
||||
( _.isUndefined(m.get('typname')) ||
|
||||
_.isNull(m.get('typname')) ||
|
||||
String(m.get('typname')).replace(/^\s+|\s+$/g, '') == '')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Check for column grid when to Add
|
||||
check_grid_add_condition: function(m) {
|
||||
var enable_flag = true;
|
||||
if(!m.inSchema.apply(this, [m])) {
|
||||
// if of_type then disable add in grid
|
||||
if (!_.isUndefined(m.get('typname')) &&
|
||||
!_.isNull(m.get('typname')) &&
|
||||
m.get('typname') !== '') {
|
||||
enable_flag = false;
|
||||
}
|
||||
}
|
||||
return enable_flag;
|
||||
},
|
||||
// Check for column grid when to edit/delete (for each row)
|
||||
check_grid_row_edit_delete: function(m) {
|
||||
var flag = true;
|
||||
if(!_.isUndefined(m.get('inheritedfrom')) &&
|
||||
!_.isNull(m.get('inheritedfrom')) &&
|
||||
String(m.get('inheritedfrom')).replace(/^\s+|\s+$/g, '') !== '') {
|
||||
flag = false;
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
// We will disable it if Inheritance is defined
|
||||
checkOfType: function(m) {
|
||||
// Disabled if it is partitioned table
|
||||
if (m.get('is_partitioned')) {
|
||||
setTimeout( function() {
|
||||
m.set('typname', undefined);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
//coll_inherits || typname
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
|
||||
(_.isUndefined(m.get('coll_inherits')) ||
|
||||
_.isNull(m.get('coll_inherits')) ||
|
||||
String(m.get('coll_inherits')).replace(/^\s+|\s+$/g, '') == '')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 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 disbale control if it's in 'edit' mode
|
||||
return !m.isNew();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isTableAutoVacuumEnable: function(m) {
|
||||
// We need to check additional condition to toggle enable/disable
|
||||
// for table auto-vacuum
|
||||
if(!m.inSchema.apply(this, [m]) &&
|
||||
m.get('autovacuum_enabled') === true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isToastTableAutoVacuumEnable: function(m) {
|
||||
// We need to check additional condition to toggle enable/disable
|
||||
// for toast table auto-vacuum
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m]) &&
|
||||
m.get('toast_autovacuum_enabled') == true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
fetch_columns_ajax: function(arg) {
|
||||
var self = this,
|
||||
url = 'get_columns',
|
||||
m = self.model.top || self.model,
|
||||
data = undefined,
|
||||
node = this.field.get('schema_node'),
|
||||
node_info = this.field.get('node_info'),
|
||||
full_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, url, this.field.get('node_data'),
|
||||
this.field.get('url_with_id') || false, node_info,
|
||||
]
|
||||
),
|
||||
cache_level = this.field.get('cache_level') || node.type,
|
||||
cache_node = this.field.get('cache_node');
|
||||
|
||||
cache_node = (cache_node && pgBrowser.Nodes['cache_node']) || node;
|
||||
|
||||
m.trigger('pgadmin:view:fetching', m, self.field);
|
||||
// Fetching Columns data for the selected table.
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: full_url,
|
||||
data: arg,
|
||||
})
|
||||
.done(function(res) {
|
||||
data = cache_node.cache(url, node_info, cache_level, res.data);
|
||||
})
|
||||
.fail(function() {
|
||||
m.trigger('pgadmin:view:fetch:error', m, self.field);
|
||||
});
|
||||
m.trigger('pgadmin:view:fetched', m, self.field);
|
||||
data = (data && data.data) || [];
|
||||
return data;
|
||||
},
|
||||
}),
|
||||
canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
// Check to whether table has disable trigger(s)
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
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 { ConstraintsSchema } from '../../../static/js/table.ui';
|
||||
import { PartitionKeysSchema, PartitionsSchema } from '../../../static/js/partition.utils.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../../static/js/privilege.ui';
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../static/js/node_ajax';
|
||||
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';
|
||||
|
||||
export function getNodePartitionTableSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
||||
const spcname = ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
|
||||
return (m.label != 'pg_global');
|
||||
});
|
||||
|
||||
let tableNode = pgBrowser.Nodes['table'];
|
||||
|
||||
return new PartitionTableSchema(
|
||||
{
|
||||
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,
|
||||
{
|
||||
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 default class PartitionTableSchema 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,
|
||||
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: [],
|
||||
partitions: [],
|
||||
partition_type: 'range',
|
||||
is_partitioned: false,
|
||||
partition_value: undefined,
|
||||
...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.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;
|
||||
}
|
||||
|
||||
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: 'advanced', label: gettext('Advanced'), type: 'group',
|
||||
// visible: ShowAdvancedTab.show_advanced_tab,
|
||||
visible: true,
|
||||
},{
|
||||
id: 'coll_inherits', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', group: gettext('Advanced'), mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'inherited_tables_cnt', label: gettext('Inherited tables count'),
|
||||
type: 'text', mode: ['properties'], group: 'advanced',
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
// Here we will create tab control for constraints
|
||||
type: 'nested-tab', group: gettext('Constraints'),
|
||||
mode: ['edit', 'create'],
|
||||
schema: obj.constraintsObj,
|
||||
},{
|
||||
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', readonly: true,
|
||||
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,
|
||||
},{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -118,17 +118,17 @@ export class PartitionsSchema extends BaseUISchema {
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'is_attach', label:gettext('Operation'), cell: 'select', type: 'select',
|
||||
minWidth: 75, options: [
|
||||
minWidth: 120, options: [
|
||||
{label: gettext('Attach'), value: true},
|
||||
{label: gettext('Create'), value: false},
|
||||
],
|
||||
], controlProps: {allowClear: false},
|
||||
editable: function(state) {
|
||||
if(obj.isNew(state) && !obj.top.isNew()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
readonly: function(state) {
|
||||
if(obj.isNew(state) && !obj.top.isNew()) {
|
||||
return false;
|
||||
}
|
||||
@@ -142,7 +142,7 @@ export class PartitionsSchema extends BaseUISchema {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
readonly: function(state) {
|
||||
if(obj.isNew(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ export class PartitionsSchema extends BaseUISchema {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function(state) {
|
||||
readonly: function(state) {
|
||||
if(obj.top && (obj.top.sessData.partition_type == 'range' ||
|
||||
obj.top.sessData.partition_type == 'list') && obj.isNew(state)
|
||||
&& obj.getServerVersion() >= 110000) {
|
||||
|
||||
@@ -535,7 +535,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
},
|
||||
{
|
||||
id: 'replica_identity', label: gettext('Replica Identity'),
|
||||
group: gettext('advanced'), type: 'text',mode: ['edit', 'properties'],
|
||||
group: 'advanced', type: 'text',mode: ['edit', 'properties'],
|
||||
}, {
|
||||
id: 'coll_inherits', label: gettext('Inherited from table(s)'),
|
||||
type: 'text', group: 'advanced', mode: ['properties'],
|
||||
|
||||
@@ -60,14 +60,6 @@ export default class VacuumSettingsSchema extends BaseUISchema {
|
||||
this.vacuumToastTableObj = new VacuumTableSchema('toast_autovacuum');
|
||||
}
|
||||
|
||||
inSchemaCheck() {
|
||||
if(this.nodeInfo && 'catalog' in this.nodeInfo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
var obj = this;
|
||||
return [{
|
||||
@@ -79,13 +71,10 @@ export default class VacuumSettingsSchema extends BaseUISchema {
|
||||
}
|
||||
// If table is partitioned table then disabled it.
|
||||
if(state.top && state.is_partitioned) {
|
||||
// We also need to unset rest of all
|
||||
state.autovacuum_custom = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(obj.inSchemaCheck)
|
||||
if(obj.inCatalog)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -107,13 +96,13 @@ export default class VacuumSettingsSchema extends BaseUISchema {
|
||||
],
|
||||
deps: ['autovacuum_custom'],
|
||||
disabled: function(state) {
|
||||
if(obj.inSchemaCheck && state.autovacuum_custom) {
|
||||
if(obj.inCatalog && state.autovacuum_custom) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
depChange: function(state) {
|
||||
if(obj.inSchemaCheck && state.autovacuum_custom) {
|
||||
if(obj.inCatalog && state.autovacuum_custom) {
|
||||
return;
|
||||
}
|
||||
return {autovacuum_enabled: 'x'};
|
||||
@@ -133,7 +122,7 @@ export default class VacuumSettingsSchema extends BaseUISchema {
|
||||
disabled: function(state) {
|
||||
// We need to check additional condition to toggle enable/disable
|
||||
// for table auto-vacuum
|
||||
if(obj.inSchemaCheck && (obj.isNew() || (state.toast_autovacuum_enabled || state.hastoasttable))) {
|
||||
if(obj.inCatalog && state.hastoasttable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -150,13 +139,13 @@ export default class VacuumSettingsSchema extends BaseUISchema {
|
||||
],
|
||||
deps:['toast_autovacuum'],
|
||||
disabled: function(state) {
|
||||
if(obj.inSchemaCheck && state.toast_autovacuum) {
|
||||
if(obj.inCatalog && state.toast_autovacuum) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
depChange: function(state) {
|
||||
if(obj.inSchemaCheck && state.toast_autovacuum) {
|
||||
if(obj.inCatalog && state.toast_autovacuum) {
|
||||
return;
|
||||
}
|
||||
if(obj.isNew() || state.hastoasttable) {
|
||||
|
||||
153
web/regression/javascript/schema_ui_files/partition.ui.spec.js
Normal file
153
web/regression/javascript/schema_ui_files/partition.ui.spec.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 _ from 'lodash';
|
||||
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
|
||||
import { getNodePartitionTableSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.ui';
|
||||
|
||||
describe('PartitionTableSchema', ()=>{
|
||||
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 = getNodePartitionTableSchema({
|
||||
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('depChange', ()=>{
|
||||
let state = {typname: 'newtype'};
|
||||
let partKeyField = _.find(schemaObj.fields, (f)=>f.id=='partition_keys');
|
||||
expect(partKeyField.depChange(state, null, null, {
|
||||
oldState: {
|
||||
typname: 'oldtype',
|
||||
}
|
||||
})).toEqual({
|
||||
partition_keys: []
|
||||
});
|
||||
|
||||
state = {
|
||||
partition_type: 'list',
|
||||
columns: [{name: 'id'}],
|
||||
partition_keys: [],
|
||||
};
|
||||
expect(partKeyField.canAddRow(state)).toBe(true);
|
||||
|
||||
state = {is_partitioned: true};
|
||||
expect(partKeyField.canAdd(state)).toBe(true);
|
||||
|
||||
expect(_.find(schemaObj.fields, (f)=>f.id=='partitions').depChange(state, ['is_partitioned']))
|
||||
.toEqual({
|
||||
partitions: []
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user