1) Added support for Default Partition. Fixes #3938
2) Ensure that record should be add/edited for root partition table with primary keys. Fixes #4104pull/22/head
parent
9c3925e448
commit
a9d964b5ca
Binary file not shown.
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 247 KiB |
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -960,17 +960,18 @@ define('pgadmin.node.table', [
|
|||
id: 'partition_key_note', label: gettext('Partition Keys'),
|
||||
type: 'note', group: 'partition', mode: ['create'],
|
||||
text: [
|
||||
'<br> ',
|
||||
'<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 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.'),
|
||||
'<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)
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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(',')
|
||||
|
|
|
@ -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 %}
|
Loading…
Reference in New Issue