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
pull/22/head
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

BIN
docs/en_US/images/table_partition.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View File

@ -14,5 +14,7 @@ Features
Bug fixes
*********
| `Bug #3938 <https://redmine.postgresql.org/issues/3938>`_ - Added support for Default Partition.
| `Bug #4104 <https://redmine.postgresql.org/issues/4104>`_ - Ensure that record should be add/edited for root partition table with primary keys.
| `Bug #4138 <https://redmine.postgresql.org/issues/4138>`_ - Fix an issue where the dropdown becomes misaligned/displaced.
| `Bug #4161 <https://redmine.postgresql.org/issues/4161>`_ - Ensure that parameters of procedures for EPAS server 10 and below should be set/reset properly.

View File

@ -442,6 +442,7 @@ icon (+) to add each partition:
* Move the *Operation* switch to *attach* to attach the partition, by default it
is *create*.
* Use the *Name* field to add the name of the partition.
* If partition type is Range or List then *Default* field will be enabled.
* If partition type is Range then *From* and *To* fields will be enabled.
* If partition type is List then *In* field will be enabled.
* If partition type is Hash then *Modulus* and *Remainder* fields will be
@ -523,4 +524,4 @@ three columns and a primary key constraint on the *category_id* column.
* Click the *Info* button (i) to access online help.
* Click the *Save* button to save work.
* Click the *Cancel* button to exit without saving work.
* Click the *Reset* button to restore configuration parameters.
* Click the *Reset* button to restore configuration parameters.

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(',')

View File

@ -0,0 +1,8 @@
{# ============= Fetch the primary keys for given object id ============= #}
{% if obj_id %}
SELECT at.attname, ty.typname
FROM pg_attribute at LEFT JOIN pg_type ty ON (ty.oid = at.atttypid)
WHERE attrelid={{obj_id}}::oid AND attnum = ANY (
(SELECT con.conkey FROM pg_class rel LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid
AND con.contype='p' WHERE rel.relkind IN ('r','s','t', 'p') AND rel.oid = {{obj_id}}::oid)::oid[])
{% endif %}