1) Added support for Default Partition. Fixes #3938

2) Ensure that record should be add/edited for root partition table with primary keys. Fixes #4104
This commit is contained in:
Khushboo Vashi
2019-04-11 13:25:24 +05:30
committed by Akshay Joshi
parent 9c3925e448
commit a9d964b5ca
8 changed files with 121 additions and 47 deletions

View File

@@ -242,6 +242,7 @@ define('pgadmin.node.table_partition_utils', [
oid: undefined,
is_attach: false,
partition_name: undefined,
is_default: undefined,
values_from: undefined,
values_to: undefined,
values_in: undefined,
@@ -252,8 +253,8 @@ define('pgadmin.node.table_partition_utils', [
schema: [{
id: 'oid', label: gettext('OID'), type: 'text',
},{
id: 'is_attach', label:gettext('Operation'), cell: 'switch',
type: 'switch', options: { 'onText': gettext('Attach'), 'offText': gettext('Create')},
id: 'is_attach', label:gettext('Operation'), cell: 'switch', type: 'switch',
options: {'onText': gettext('Attach'), 'offText': gettext('Create'), 'width': 65},
cellHeaderClasses: 'width_percent_5',
editable: function(m) {
if (m instanceof Backbone.Model && m.isNew() && !m.top.isNew())
@@ -268,39 +269,53 @@ define('pgadmin.node.table_partition_utils', [
return true;
return false;
}, cellFunction: getPartitionCell,
},{
id: 'is_default', label: gettext('Default'), type: 'switch', cell:'switch',
cellHeaderClasses: 'width_percent_5', min_version: 110000,
options: {'onText': gettext('Yes'), 'offText': gettext('No')},
editable: function(m) {
if(m.handler && m.handler.top &&
m.handler.top.attributes &&
(m.handler.top.attributes.partition_type === 'range' ||
m.handler.top.attributes.partition_type === 'list') &&
m instanceof Backbone.Model && m.isNew() &&
m.handler.top.node_info.server.version >= 110000)
return true;
return false;
},
},{
id: 'values_from', label: gettext('From'), type:'text',
cell:Backgrid.Extension.StringDepCell,
cell:Backgrid.Extension.StringDepCell, deps: ['is_default'],
cellHeaderClasses: 'width_percent_15',
editable: function(m) {
if(m.handler && m.handler.top &&
m.handler.top.attributes &&
m.handler.top.attributes.partition_type === 'range' &&
m instanceof Backbone.Model && m.isNew())
m instanceof Backbone.Model && m.isNew() && m.get('is_default') !== true)
return true;
return false;
},
},{
id: 'values_to', label: gettext('To'), type:'text',
cell:Backgrid.Extension.StringDepCell,
cell:Backgrid.Extension.StringDepCell, deps: ['is_default'],
cellHeaderClasses: 'width_percent_15',
editable: function(m) {
if(m.handler && m.handler.top &&
m.handler.top.attributes &&
m.handler.top.attributes.partition_type === 'range' &&
m instanceof Backbone.Model && m.isNew())
m instanceof Backbone.Model && m.isNew() && m.get('is_default') !== true)
return true;
return false;
},
},{
id: 'values_in', label: gettext('In'), type:'text',
cell:Backgrid.Extension.StringDepCell,
cell:Backgrid.Extension.StringDepCell, deps: ['is_default'],
cellHeaderClasses: 'width_percent_15',
editable: function(m) {
if(m.handler && m.handler.top &&
m.handler.top.attributes &&
m.handler.top.attributes.partition_type === 'list' &&
m instanceof Backbone.Model && m.isNew())
m instanceof Backbone.Model && m.isNew() && m.get('is_default') !== true)
return true;
return false;
},
@@ -331,6 +346,7 @@ define('pgadmin.node.table_partition_utils', [
}],
validate: function() {
var partition_name = this.get('partition_name'),
is_default = this.get('is_default'),
values_from = this.get('values_from'),
values_to = this.get('values_to'),
values_in = this.get('values_in'),
@@ -350,20 +366,20 @@ define('pgadmin.node.table_partition_utils', [
}
if (this.top.get('partition_type') === 'range') {
if (_.isUndefined(values_from) || _.isNull(values_from) ||
String(values_from).replace(/^\s+|\s+$/g, '') === '') {
if (is_default !== true && (_.isUndefined(values_from) ||
_.isNull(values_from) || String(values_from).replace(/^\s+|\s+$/g, '') === '')) {
msg = gettext('For range partition From field cannot be empty.');
this.errorModel.set('values_from', msg);
return msg;
} else if (_.isUndefined(values_to) || _.isNull(values_to) ||
String(values_to).replace(/^\s+|\s+$/g, '') === '') {
} else if (is_default !== true && (_.isUndefined(values_to) || _.isNull(values_to) ||
String(values_to).replace(/^\s+|\s+$/g, '') === '')) {
msg = gettext('For range partition To field cannot be empty.');
this.errorModel.set('values_to', msg);
return msg;
}
} else if (this.top.get('partition_type') === 'list') {
if (_.isUndefined(values_in) || _.isNull(values_in) ||
String(values_in).replace(/^\s+|\s+$/g, '') === '') {
if (is_default !== true && (_.isUndefined(values_in) || _.isNull(values_in) ||
String(values_in).replace(/^\s+|\s+$/g, '') === '')) {
msg = gettext('For list partition In field cannot be empty.');
this.errorModel.set('values_in', msg);
return msg;

View File

@@ -960,17 +960,18 @@ define('pgadmin.node.table', [
id: 'partition_key_note', label: gettext('Partition Keys'),
type: 'note', group: 'partition', mode: ['create'],
text: [
'<br>&nbsp;&nbsp;',
'<ul><li>',
gettext('Partition table supports two types of keys:'),
'<br><ul><li>',
gettext('Column: User can select any column from the list of available columns.'),
'</li><li>',
gettext('Expression: User can specify expression to create partition key.'),
'<br><p>',
gettext('Example'),
':',
'<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.'),
'</p></li></ul>',
'</li></ul>',
].join(''),
visible: function(m) {
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
@@ -990,7 +991,7 @@ define('pgadmin.node.table', [
canEdit: false, canDelete: true,
customDeleteTitle: gettext('Detach Partition'),
customDeleteMsg: gettext('Are you sure you wish to detach this partition?'),
columns:['is_attach', 'partition_name', 'values_from', 'values_to', 'values_in', 'values_modulus', 'values_remainder'],
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() {
@@ -1053,22 +1054,28 @@ define('pgadmin.node.table', [
id: 'partition_note', label: gettext('Partitions'),
type: 'note', group: 'partition',
text: [
'<ul>',
' <li>',
gettext('Create a table: User can create multiple partitions while creating new partitioned table. Operation switch is disabled in this scenario.'),
'<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>',
gettext('Edit existing table: User can create/attach/detach multiple partitions. In attach operation user can select table from the list of suitable tables to be attached.'),
'<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 partitions 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.'),
'<br>',
gettext('Example'),
':<ul><li>',
gettext('From/To: Enabled for range partition. Consider partitioned table with multiple keys of type Integer, then values should be specified like \'100\',\'200\'.'),
'</li><li> ',
gettext('In: Enabled for list partition. Values must be comma(,) separated and quoted with single quote.'),
'</li></ul></li></ul>',
gettext('Modulus/Remainder: Enabled for hash partition.'),
'</li></ul></li></ul>',
'</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)

View File

@@ -31,6 +31,14 @@ class TableAddTestCase(BaseTestGenerator):
partition_type='range'
)
),
('Create Range partitioned table with 1 default and 2'
' value based partition',
dict(url='/browser/table/obj/',
server_min_version=110000,
partition_type='range',
is_default=True
)
),
('Create List partitioned table with 2 partitions',
dict(url='/browser/table/obj/',
server_min_version=100000,
@@ -215,6 +223,24 @@ class TableAddTestCase(BaseTestGenerator):
'is_attach': False,
'partition_name': 'emp_2011'
}]
if hasattr(self, 'is_default'):
data['partitions'] = \
[{'values_from': "'2010-01-01'",
'values_to': "'2010-12-31'",
'is_attach': False,
'partition_name': 'emp_2010_def'
},
{'values_from': "'2011-01-01'",
'values_to': "'2011-12-31'",
'is_attach': False,
'partition_name': 'emp_2011_def'
},
{'values_from': "",
'values_to': "",
'is_attach': False,
'is_default': True,
'partition_name': 'emp_2012_def'
}]
data['partition_keys'] = \
[{'key_type': 'column', 'pt_column': 'DOJ'}]
elif self.partition_type == 'list':

View File

@@ -2184,16 +2184,23 @@ class BaseTableView(PGChildNodeView, BasePartitionTable):
partition_name = row['schema_name'] + '.' + row['name']
if data['partition_type'] == 'range':
range_part = row['partition_value'].split(
'FOR VALUES FROM (')[1].split(') TO')
range_from = range_part[0]
range_to = range_part[1][2:-1]
if row['partition_value'] == 'DEFAULT':
is_default = True
range_from = None
range_to = None
else:
range_part = row['partition_value'].split(
'FOR VALUES FROM (')[1].split(') TO')
range_from = range_part[0]
range_to = range_part[1][2:-1]
is_default = False
partitions.append({
'oid': row['oid'],
'partition_name': partition_name,
'values_from': range_from,
'values_to': range_to
'values_to': range_to,
'is_default': is_default
})
elif data['partition_type'] == 'list':
range_part = \
@@ -2251,15 +2258,22 @@ class BaseTableView(PGChildNodeView, BasePartitionTable):
part_data['relispartition'] = True
part_data['name'] = row['partition_name']
if partitions['partition_type'] == 'range':
if 'is_default' in row and row['is_default'] and (
partitions['partition_type'] == 'range' or
partitions['partition_type'] == 'list'):
part_data['partition_value'] = 'DEFAULT'
elif partitions['partition_type'] == 'range':
range_from = row['values_from'].split(',')
range_to = row['values_to'].split(',')
from_str = ', '.join("{0}".format(item) for item in range_from)
to_str = ', '.join("{0}".format(item) for item in range_to)
from_str = ', '.join("{0}".format(item) for
item in range_from)
to_str = ', '.join("{0}".format(item) for
item in range_to)
part_data['partition_value'] = 'FOR VALUES FROM (' + from_str \
+ ') TO (' + to_str + ')'
part_data['partition_value'] = 'FOR VALUES FROM (' +\
from_str + ') TO (' +\
to_str + ')'
elif partitions['partition_type'] == 'list':
range_in = row['values_in'].split(',')