diff --git a/docs/en_US/images/table_partition.png b/docs/en_US/images/table_partition.png old mode 100755 new mode 100644 index 6d4cf7855..195ebf2b8 Binary files a/docs/en_US/images/table_partition.png and b/docs/en_US/images/table_partition.png differ diff --git a/docs/en_US/release_notes_4_6.rst b/docs/en_US/release_notes_4_6.rst index 64e90b9b2..e320adb63 100644 --- a/docs/en_US/release_notes_4_6.rst +++ b/docs/en_US/release_notes_4_6.rst @@ -14,5 +14,7 @@ Features Bug fixes ********* +| `Bug #3938 `_ - Added support for Default Partition. +| `Bug #4104 `_ - Ensure that record should be add/edited for root partition table with primary keys. | `Bug #4138 `_ - Fix an issue where the dropdown becomes misaligned/displaced. | `Bug #4161 `_ - Ensure that parameters of procedures for EPAS server 10 and below should be set/reset properly. \ No newline at end of file diff --git a/docs/en_US/table_dialog.rst b/docs/en_US/table_dialog.rst index 6752f8fde..3d0cc9772 100644 --- a/docs/en_US/table_dialog.rst +++ b/docs/en_US/table_dialog.rst @@ -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. \ No newline at end of file +* Click the *Reset* button to restore configuration parameters. diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js index 568556113..54c29619e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/partition.utils.js @@ -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; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js index a76a4facb..10c0cfda7 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js @@ -960,17 +960,18 @@ define('pgadmin.node.table', [ id: 'partition_key_note', label: gettext('Partition Keys'), type: 'note', group: 'partition', mode: ['create'], text: [ - '
  ', + '
  • ', gettext('Partition table supports two types of keys:'), - '
    • ', - gettext('Column: User can select any column from the list of available columns.'), '
    • ', - gettext('Expression: User can specify expression to create partition key.'), - '

      ', - gettext('Example'), - ':', + '', gettext('Column: '), '', + gettext('User can select any column from the list of available columns.'), + '

    • ', + '', gettext('Expression: '), '', + gettext('User can specify expression to create partition key.'), + '
    • ', + '', gettext('Example: '), '', 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.'), - '

    ', + '
', ].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: [ - '
    ', - '
  • ', - gettext('Create a table: User can create multiple partitions while creating new partitioned table. Operation switch is disabled in this scenario.'), + '
    • ', + '', gettext('Create a table: '), '', + gettext('User can create multiple partitions while creating new partitioned table. Operation switch is disabled in this scenario.'), '
    • ', - 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.'), + '', gettext('Edit existing table: '), '', + gettext('User can create/attach/detach multiple partitions. In attach operation user can select table from the list of suitable tables to be attached.'), '
    • ', + '', gettext('Default: '), '', + gettext('The default partition can store rows that do not fall into any existing partition’s range or list.'), + '
    • ', + '', gettext('From/To/In input: '), '', 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.'), - '
      ', - gettext('Example'), - ':
      • ', - gettext('From/To: Enabled for range partition. Consider partitioned table with multiple keys of type Integer, then values should be specified like \'100\',\'200\'.'), - '
      • ', - gettext('In: Enabled for list partition. Values must be comma(,) separated and quoted with single quote.'), - '
    ', - gettext('Modulus/Remainder: Enabled for hash partition.'), - '
', + '
  • ', + '', gettext('Example: From/To: '), '', + gettext('Enabled for range partition. Consider partitioned table with multiple keys of type Integer, then values should be specified like \'100\',\'200\'.'), + '
  • ', + '', gettext('In: '), '', + gettext('Enabled for list partition. Values must be comma(,) separated and quoted with single quote.'), + '
  • ', + '', gettext('Modulus/Remainder: '), '', + gettext('Enabled for hash partition.'), + '
  • ', ].join(''), visible: function(m) { if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_add.py index a0ee77a49..6b97b253f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_add.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_add.py @@ -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': diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py index 78b250477..7815b3252 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py @@ -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(',') diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/11_plus/primary_keys.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/11_plus/primary_keys.sql new file mode 100644 index 000000000..1dfb094f7 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/11_plus/primary_keys.sql @@ -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 %}