public inbox for [email protected]
help / color / mirror / Atom feedFrom: Akshay Joshi <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin4][Patch]: Fix for Feature #4334, RM #4496 and #4497
Date: Wed, 24 Jul 2019 12:39:47 +0530
Message-ID: <CANxoLDcbVUZUjQiS56g07EHaR3+5340P9QjpES2GC_JoRd5+gg@mail.gmail.com> (raw)
Hi Hackers,
I have added the support of Generated Columns (PG/EPAS 12) and also fixed
RM #4496, #4497.
During the implementation of that I have also fixed following issues that I
encountered:
- User should not be able to provide Default value with Identity column.
- 'Drop Cascade' button to delete multiple columns should be disabled.
- Set "Not Null?" switch to 'Yes' when we create Identity column.
- Fix issue when we insert default value for the column and then remove
it. It generates wrong msql.
- Fix issue when we provide the default value of a column and then
select the datatype to serial/bigserial/smallserial, it generates wrong
msql.
Modified/Added regression test cases for adding Identity column and
Generated columns.
Please review it.
--
*Thanks & Regards*
*Akshay Joshi*
*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*
Attachments:
[application/octet-stream] RM_4334_4496_4497.patch (71.2K, 3-RM_4334_4496_4497.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py
index fdf72d73..28420fa9 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py
@@ -547,7 +547,13 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
)
for k, v in data.items():
- data[k] = json.loads(v, encoding='utf-8', cls=ColParamsJSONDecoder)
+ # comments should be taken as is because if user enters a
+ # json comment it is parsed by loads which should not happen
+ if k in ('description',):
+ data[k] = v
+ else:
+ data[k] = json.loads(v, encoding='utf-8',
+ cls=ColParamsJSONDecoder)
required_args = {
'name': 'Name',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.js
index 3ece03f7..a045dc78 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.js
@@ -24,7 +24,7 @@ define('pgadmin.node.column', [
type: 'coll-column',
columns: ['name', 'atttypid', 'description'],
canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
- canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
+ canDropCascade: false,
});
}
@@ -185,13 +185,15 @@ define('pgadmin.node.column', [
attnotnull: false,
attlen: null,
attprecision: null,
- attidentity: undefined,
+ attidentity: 'a',
seqincrement: undefined,
seqstart: undefined,
seqmin: undefined,
seqmax: undefined,
seqcache: undefined,
seqcycle: undefined,
+ colconstype: 'n',
+ genexpr: undefined,
},
initialize: function(attrs) {
if (_.size(attrs) !== 0) {
@@ -479,67 +481,6 @@ define('pgadmin.node.column', [
}
return flag;
},
- },{
- id: 'defval', label: gettext('Default'), cell: 'string',
- type: 'text', group: gettext('Definition'), deps: ['cltype'],
- disabled: function(m) {
- if(!m.inSchemaWithModelCheck.apply(this, [m])) {
- var type = m.get('cltype');
- return type == 'serial' || type == 'bigserial'
- || type == 'smallserial';
- }
- },
- },{
- id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
- type: 'switch', disabled: 'inSchemaWithColumnCheck', cellHeaderClasses:'width_percent_20',
- group: gettext('Definition'), editable: 'editable_check_for_table',
- options: { onText: gettext('Yes'), offText: gettext('No'), onColor: 'success', offColor: 'primary' },
- },{
- type: 'nested', control: 'fieldset', label: gettext('Identity'),
- group: gettext('Definition'),
- schema:[{
- id: 'attidentity', label: gettext('Identity'), control: 'select2',
- cell: 'select2', select2: { placeholder: 'Select identity',
- allowClear: true,
- width: '100%',
- },
- min_version: 100000, group: gettext('Identity'),
- 'options': [
- {label: gettext('ALWAYS'), value: 'a'},
- {label: gettext('BY DEFAULT'), value: 'd'},
- ],
- },{
- id: 'seqincrement', label: gettext('Increment'), type: 'int',
- mode: ['properties', 'create', 'edit'], group: gettext('Identity'),
- min: 1, deps: ['attidentity'], disabled: 'isIdentityColumn',
- },{
- id: 'seqstart', label: gettext('Start'), type: 'int',
- mode: ['properties', 'create'], group: gettext('Identity'),
- disabled: function(m) {
- if (!m.isNew())
- return true;
- let isIdentity = m.get('attidentity');
- if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
- return false;
- return true;
- }, deps: ['attidentity'],
- },{
- id: 'seqmin', label: gettext('Minimum'), type: 'int',
- mode: ['properties', 'create', 'edit'], group: gettext('Identity'),
- deps: ['attidentity'], disabled: 'isIdentityColumn',
- },{
- id: 'seqmax', label: gettext('Maximum'), type: 'int',
- mode: ['properties', 'create', 'edit'], group: gettext('Identity'),
- deps: ['attidentity'], disabled: 'isIdentityColumn',
- },{
- id: 'seqcache', label: gettext('Cache'), type: 'int',
- mode: ['properties', 'create', 'edit'], group: gettext('Identity'),
- min: 1, deps: ['attidentity'], disabled: 'isIdentityColumn',
- },{
- id: 'seqcycle', label: gettext('Cycled'), type: 'switch',
- mode: ['properties', 'create', 'edit'], group: gettext('Identity'),
- deps: ['attidentity'], disabled: 'isIdentityColumn',
- }],
},{
id: 'attstattarget', label: gettext('Statistics'), cell: 'string',
type: 'text', disabled: 'inSchemaWithColumnCheck', mode: ['properties', 'edit'],
@@ -558,6 +499,149 @@ define('pgadmin.node.column', [
{label: 'EXTERNAL', value: 'e'},
{label: 'EXTENDED', value: 'x'},
],
+ },{
+ id: 'defval', label: gettext('Default'), cell: 'string',
+ type: 'text', group: gettext('Constraints'), deps: ['cltype', 'colconstype'],
+ disabled: function(m) {
+ var is_disabled = false;
+ if(!m.inSchemaWithModelCheck.apply(this, [m])) {
+ var type = m.get('cltype');
+ is_disabled = (type == 'serial' || type == 'bigserial' || type == 'smallserial');
+ }
+
+ is_disabled = is_disabled || m.get('colconstype') != 'n';
+ if (is_disabled && m.isNew()) {
+ setTimeout(function () {
+ m.set('defval', undefined);
+ }, 10);
+ }
+
+ return is_disabled;
+ },
+ },{
+ id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
+ type: 'switch', cellHeaderClasses:'width_percent_20',
+ group: gettext('Constraints'), editable: 'editable_check_for_table',
+ options: { onText: gettext('Yes'), offText: gettext('No'), onColor: 'success', offColor: 'primary' },
+ deps: ['colconstype'],
+ disabled: function(m) {
+ if (m.get('colconstype') == 'i') {
+ setTimeout(function () {
+ m.set('attnotnull', true);
+ }, 10);
+ }
+ return m.inSchemaWithColumnCheck(m);
+ },
+ }, {
+ id: 'colconstype',
+ label: gettext('Type'),
+ cell: 'string',
+ type: 'radioModern',
+ controlsClassName: 'pgadmin-controls col-12 col-sm-9',
+ controlLabelClassName: 'control-label col-sm-3 col-12',
+ group: gettext('Constraints'),
+ options: function(m) {
+ var opt_array = [
+ {'label': gettext('NONE'), 'value': 'n'},
+ {'label': gettext('IDENTITY'), 'value': 'i'},
+ ];
+
+ if (m.top.node_info && m.top.node_info.server &&
+ m.top.node_info.server.version >= 120000) {
+ // You can't change the existing column to Generated column.
+ if (m.isNew()) {
+ opt_array.push({
+ 'label': gettext('GENERATED'),
+ 'value': 'g',
+ });
+ } else {
+ opt_array.push({
+ 'label': gettext('GENERATED'),
+ 'value': 'g',
+ 'disabled': true,
+ });
+ }
+ }
+
+ return opt_array;
+ },
+ disabled: function(m) {
+ if (!m.isNew() && m.get('colconstype') == 'g') {
+ return true;
+ }
+ return false;
+ },
+ visible: function(m) {
+ if (m.top.node_info && m.top.node_info.server &&
+ m.top.node_info.server.version >= 100000) {
+ return true;
+ }
+ return false;
+ },
+ }, {
+ id: 'attidentity', label: gettext('Identity'), control: 'select2',
+ cell: 'select2',
+ select2: {placeholder: 'Select identity', allowClear: false, width: '100%'},
+ min_version: 100000, group: gettext('Constraints'),
+ 'options': [
+ {label: gettext('ALWAYS'), value: 'a'},
+ {label: gettext('BY DEFAULT'), value: 'd'},
+ ],
+ deps: ['colconstype'], visible: 'isTypeIdentity',
+ disabled: function(m) {
+ if (!m.isNew()) {
+ if (m.get('attidentity') == '' && m.get('colconstype') == 'i') {
+ setTimeout(function () {
+ m.set('attidentity', m.get('old_attidentity'));
+ }, 10);
+ }
+ }
+ return false;
+ },
+ }, {
+ id: 'seqincrement', label: gettext('Increment'), type: 'int',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
+ visible: 'isTypeIdentity',
+ },{
+ id: 'seqstart', label: gettext('Start'), type: 'int',
+ mode: ['properties', 'create'], group: gettext('Constraints'),
+ disabled: function(m) {
+ if (!m.isNew())
+ return true;
+ let isIdentity = m.get('attidentity');
+ if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
+ return false;
+ return true;
+ }, deps: ['attidentity', 'colconstype'],
+ visible: 'isTypeIdentity',
+ },{
+ id: 'seqmin', label: gettext('Minimum'), type: 'int',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
+ visible: 'isTypeIdentity',
+ },{
+ id: 'seqmax', label: gettext('Maximum'), type: 'int',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
+ visible: 'isTypeIdentity',
+ },{
+ id: 'seqcache', label: gettext('Cache'), type: 'int',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
+ visible: 'isTypeIdentity',
+ },{
+ id: 'seqcycle', label: gettext('Cycled'), type: 'switch',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
+ visible: 'isTypeIdentity',
+ },{
+ id: 'genexpr', label: gettext('Expression'), type: 'text',
+ mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
+ min_version: 120000, deps: ['colconstype'], visible: 'isTypeGenerated',
+ disabled: function(m) {
+ return !m.isNew();
+ },
},{
id: 'is_pk', label: gettext('Primary key?'),
type: 'switch', disabled: true, mode: ['properties'],
@@ -665,12 +749,23 @@ define('pgadmin.node.column', [
}
}
+ let genexpr = this.get('genexpr');
+ if (this.get('colconstype') == 'g' &&
+ (_.isUndefined(genexpr) || _.isNull(genexpr) || genexpr == '')) {
+ msg = gettext('Expression value cannot be empty.');
+ this.errorModel.set('genexpr', msg);
+ return msg;
+ } else {
+ this.errorModel.unset('genexpr');
+ }
+
var minimum = this.get('seqmin'),
maximum = this.get('seqmax'),
start = this.get('seqstart');
- if (!this.isNew() && (this.get('old_attidentity') == 'a' || this.get('old_attidentity') == 'd') &&
- (this.get('attidentity') == 'a' || this.get('attidentity') == 'd')) {
+ if (!this.isNew() && this.get('colconstype') == 'i' &&
+ (this.get('old_attidentity') == 'a' || this.get('old_attidentity') == 'd') &&
+ (this.get('attidentity') == 'a' || this.get('attidentity') == 'd')) {
if (_.isUndefined(this.get('seqincrement'))
|| String(this.get('seqincrement')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Increment value cannot be empty.');
@@ -745,6 +840,22 @@ define('pgadmin.node.column', [
return false;
return true;
},
+ // Check whether the column is a identity column
+ isTypeIdentity: function(m) {
+ let colconstype = m.get('colconstype');
+ if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'i') {
+ return true;
+ }
+ return false;
+ },
+ // Check whether the column is a generated column
+ isTypeGenerated: function(m) {
+ let colconstype = m.get('colconstype');
+ if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'g') {
+ return true;
+ }
+ return false;
+ },
// We will check if we are under schema node & in 'create' mode
notInSchema: function() {
if(this.node_info && 'catalog' in this.node_info)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_add.py
index bb92cfb7..756fb510 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_add.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_add.py
@@ -25,31 +25,83 @@ from pgadmin.utils import server_utils as server_utils
class ColumnAddTestCase(BaseTestGenerator):
"""This class will add new column under table node."""
scenarios = [
- ('Add column', dict(url='/browser/column/obj/')),
- ('Add column with Identity', dict(url='/browser/column/obj/',
- server_min_version=100000,
- identity_opt={
- 'cltype': 'bigint',
- 'attidentity': 'a',
- 'seqincrement': 1,
- 'seqstart': 1,
- 'seqmin': 1,
- 'seqmax': 10,
- 'seqcache': 1,
- 'seqcycle': True
- })),
- ('Add column with Identity', dict(url='/browser/column/obj/',
- server_min_version=100000,
- identity_opt={
- 'cltype': 'bigint',
- 'attidentity': 'd',
- 'seqincrement': 2,
- 'seqstart': 2,
- 'seqmin': 2,
- 'seqmax': 2000,
- 'seqcache': 1,
- 'seqcycle': True
- }))
+ ('Add column', dict(
+ url='/browser/column/obj/',
+ data={
+ 'cltype': "\"char\"",
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': False,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions':[],
+ 'seclabels':[],
+ })),
+ ('Add Identity column with Always', dict(
+ url='/browser/column/obj/',
+ server_min_version=100000,
+ skip_msg='Identity column are not supported by EPAS/PG 10.0 '
+ 'and below.',
+ data={
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'i',
+ 'attidentity': 'a',
+ 'seqincrement': 1,
+ 'seqstart': 1,
+ 'seqmin': 1,
+ 'seqmax': 10,
+ 'seqcache': 1,
+ 'seqcycle': True
+ })),
+ ('Add Identity column with As Default', dict(
+ url='/browser/column/obj/',
+ col_data_type='bigint',
+ server_min_version=100000,
+ skip_msg='Identity column are not supported by EPAS/PG 10.0 '
+ 'and below.',
+ data={
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'i',
+ 'attidentity': 'd',
+ 'seqincrement': 2,
+ 'seqstart': 2,
+ 'seqmin': 2,
+ 'seqmax': 2000,
+ 'seqcache': 1,
+ 'seqcycle': True
+ })),
+ ('Add Generated column', dict(
+ url='/browser/column/obj/',
+ col_data_type='bigint',
+ server_min_version=120000,
+ skip_msg='Generated column are not supported by EPAS/PG 12.0 '
+ 'and below.',
+ data={
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'g',
+ 'genexpr': '100 * 100'
+ })),
]
def setUp(self):
@@ -64,9 +116,7 @@ class ColumnAddTestCase(BaseTestGenerator):
raise Exception("Could not connect to server to add "
"a table.")
if server_con["data"]["version"] < self.server_min_version:
- message = "Identity columns are not supported by " \
- "PPAS/PG 10.0 and below."
- self.skipTest(message)
+ self.skipTest(self.skip_msg)
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.server_id, self.db_id)
@@ -87,26 +137,16 @@ class ColumnAddTestCase(BaseTestGenerator):
def runTest(self):
"""This function will add column under table node."""
self.column_name = "test_column_add_%s" % (str(uuid.uuid4())[1:8])
- data = {
- "name": self.column_name,
- "cltype": "\"char\"",
- "attacl": [],
- "is_primary_key": False,
- "attnotnull": False,
- "attlen": None,
- "attprecision": None,
- "attoptions": [],
- "seclabels": []
- }
+ self.data.update({
+ 'name': self.column_name
+ })
- if hasattr(self, 'identity_opt'):
- data.update(self.identity_opt)
# Add table
response = self.tester.post(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' + str(self.db_id) +
'/' + str(self.schema_id) + '/' + str(self.table_id) + '/',
- data=json.dumps(data),
+ data=json.dumps(self.data),
content_type='html/json')
self.assertEquals(response.status_code, 200)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_put.py
index 43071ab8..8bb89b64 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_put.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_column_put.py
@@ -26,32 +26,54 @@ from pgadmin.utils import server_utils as server_utils
class ColumnPutTestCase(BaseTestGenerator):
"""This class will update the column under table node."""
scenarios = [
- ('Edit column Node URL', dict(url='/browser/column/obj/',
- col_data_type='char')),
- ('Edit column with Identity', dict(url='/browser/column/obj/',
- col_data_type='bigint',
- server_min_version=100000,
- identity_opt={
- 'attidentity': 'a',
- 'seqincrement': 1,
- 'seqstart': 1,
- 'seqmin': 1,
- 'seqmax': 10,
- 'seqcache': 1,
- 'seqcycle': True
- })),
- ('Edit column with Identity', dict(url='/browser/column/obj/',
- server_min_version=100000,
- col_data_type='bigint',
- identity_opt={
- 'attidentity': 'd',
- 'seqincrement': 2,
- 'seqstart': 2,
- 'seqmin': 2,
- 'seqmax': 2000,
- 'seqcache': 1,
- 'seqcycle': True
- }))
+ ('Edit column comments and null constraints', dict(
+ url='/browser/column/obj/',
+ col_data_type='char',
+ data={
+ 'attnotnull': True,
+ 'description': "This is test comment for column"
+ })),
+ ('Edit column to Identity column as Always', dict(
+ url='/browser/column/obj/',
+ col_data_type='bigint',
+ server_min_version=100000,
+ skip_msg='Identity column are not supported by EPAS/PG 10.0 '
+ 'and below.',
+ data={
+ 'attnotnull': True,
+ 'attidentity': 'a',
+ 'seqincrement': 1,
+ 'seqstart': 1,
+ 'seqmin': 1,
+ 'seqmax': 10,
+ 'seqcache': 1,
+ 'seqcycle': True
+ })),
+ ('Edit column to Identity column as Default', dict(
+ url='/browser/column/obj/',
+ col_data_type='bigint',
+ server_min_version=100000,
+ skip_msg='Identity column are not supported by EPAS/PG 10.0 '
+ 'and below.',
+ data={
+ 'attnotnull': True,
+ 'attidentity': 'd',
+ 'seqincrement': 2,
+ 'seqstart': 2,
+ 'seqmin': 2,
+ 'seqmax': 2000,
+ 'seqcache': 1,
+ 'seqcycle': True
+ })),
+ ('Edit column Drop Identity by changing constraint type to NONE',
+ dict(url='/browser/column/obj/',
+ col_data_type='bigint',
+ server_min_version=100000,
+ create_identity_column=True,
+ skip_msg='Identity column are not supported by EPAS/PG 10.0 '
+ 'and below.',
+ data={'colconstype': 'n'})
+ )
]
def setUp(self):
@@ -66,9 +88,7 @@ class ColumnPutTestCase(BaseTestGenerator):
raise Exception("Could not connect to server to add "
"a table.")
if server_con["data"]["version"] < self.server_min_version:
- message = "Identity columns are not supported by " \
- "PPAS/PG 10.0 and below."
- self.skipTest(message)
+ self.skipTest(self.skip_msg)
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.server_id, self.db_id)
@@ -86,12 +106,16 @@ class ColumnPutTestCase(BaseTestGenerator):
self.schema_name,
self.table_name)
self.column_name = "test_column_put_%s" % (str(uuid.uuid4())[1:8])
- self.column_id = columns_utils.create_column(self.server,
- self.db_name,
- self.schema_name,
- self.table_name,
- self.column_name,
- self.col_data_type)
+
+ if hasattr(self, 'create_identity_column') and \
+ self.create_identity_column:
+ self.column_id = columns_utils.create_identity_column(
+ self.server, self.db_name, self.schema_name,
+ self.table_name, self.column_name, self.col_data_type)
+ else:
+ self.column_id = columns_utils.create_column(
+ self.server, self.db_name, self.schema_name,
+ self.table_name, self.column_name, self.col_data_type)
def runTest(self):
"""This function will update the column under table node."""
@@ -99,14 +123,11 @@ class ColumnPutTestCase(BaseTestGenerator):
self.column_name)
if not col_response:
raise Exception("Could not find the column to update.")
- data = {
- "attnum": self.column_id,
- "name": self.column_name,
- "attnotnull": True,
- "description": "This is test comment for column"
- }
- if hasattr(self, 'identity_opt'):
- data.update(self.identity_opt)
+ self.data.update({
+ 'attnum': self.column_id,
+ 'name': self.column_name,
+ })
+
response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' +
@@ -114,7 +135,7 @@ class ColumnPutTestCase(BaseTestGenerator):
str(self.schema_id) + '/' +
str(self.table_id) + '/' +
str(self.column_id),
- data=json.dumps(data),
+ data=json.dumps(self.data),
follow_redirects=True)
self.assertEquals(response.status_code, 200)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/utils.py
index e013699f..27f9a430 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/utils.py
@@ -63,6 +63,55 @@ def create_column(server, db_name, schema_name, table_name, col_name,
raise
+def create_identity_column(server, db_name, schema_name, table_name,
+ col_name, col_data_type='bigint'):
+ """
+ This function creates a column under provided table.
+ :param server: server details
+ :type server: dict
+ :param db_name: database name
+ :type db_name: str
+ :param schema_name: schema name
+ :type schema_name: str
+ :param table_name: table name
+ :type table_name: str
+ :param col_name: column name
+ :type col_name: str
+ :param col_data_type: column data type
+ :type col_data_type: str
+ :return table_id: table id
+ :rtype: int
+ """
+ try:
+ connection = utils.get_db_connection(db_name,
+ server['username'],
+ server['db_password'],
+ server['host'],
+ server['port'],
+ server['sslmode'])
+ old_isolation_level = connection.isolation_level
+ connection.set_isolation_level(0)
+ pg_cursor = connection.cursor()
+ query = "ALTER TABLE %s.%s ADD COLUMN %s %s " \
+ "GENERATED ALWAYS AS IDENTITY" % \
+ (schema_name, table_name, col_name, col_data_type)
+ pg_cursor.execute(query)
+ connection.set_isolation_level(old_isolation_level)
+ connection.commit()
+ # Get column position of newly added column
+ pg_cursor.execute("select attnum from pg_attribute where"
+ " attname='%s'" % col_name)
+ col = pg_cursor.fetchone()
+ col_pos = ''
+ if col:
+ col_pos = col[0]
+ connection.close()
+ return col_pos
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ raise
+
+
def verify_column(server, db_name, col_name):
"""
This function verifies table exist in database or not.
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/create.sql
index e87d1e2e..0fba3369 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/create.sql
@@ -7,10 +7,10 @@
ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ADD COLUMN {{conn|qtIdent(data.name)}} {% if is_sql %}{{data.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, data.cltype, data.attlen, data.attprecision, data.hasSqrBracket) }}{% endif %}{% if data.collspcname %}
COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %}
- NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none %}
- DEFAULT {{data.defval}}{% endif %}{% if data.attidentity and data.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif data.attidentity and data.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
+ NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none and data.defval != '' %}
+ DEFAULT {{data.defval}}{% endif %}{% if data.colconstype == 'i' %}{% if data.attidentity and data.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif data.attidentity and data.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
{% if data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache %} ( {% endif %}
-{% if data.seqincrement is defined and data.seqcycle %}
+{% if data.seqcycle is defined and data.seqcycle %}
CYCLE {% endif %}{% if data.seqincrement is defined and data.seqincrement|int(-1) > -1 %}
INCREMENT {{data.seqincrement|int}} {% endif %}{% if data.seqstart is defined and data.seqstart|int(-1) > -1%}
START {{data.seqstart|int}} {% endif %}{% if data.seqmin is defined and data.seqmin|int(-1) > -1%}
@@ -18,7 +18,7 @@ MINVALUE {{data.seqmin|int}} {% endif %}{% if data.seqmax is defined and data.se
MAXVALUE {{data.seqmax|int}} {% endif %}{% if data.seqcache is defined and data.seqcache|int(-1) > -1%}
CACHE {{data.seqcache|int}} {% endif %}
{% if data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache %}){% endif %}
-{% endif %};
+{% endif %}{% endif %};
{### Add comments ###}
{% if data and data.description %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/properties.sql
index acfccb09..14ebf579 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/properties.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/properties.sql
@@ -19,6 +19,7 @@ SELECT att.attname as name, att.*, def.*, pg_catalog.pg_get_expr(def.adbin, def.
EXISTS(SELECT 1 FROM pg_constraint WHERE conrelid=att.attrelid AND contype='f' AND att.attnum=ANY(conkey)) As is_fk,
(SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=att.attrelid AND sl1.objsubid=att.attnum) AS seclabels,
(CASE WHEN (att.attnum < 1) THEN true ElSE false END) AS is_sys_column,
+ (CASE WHEN (att.attidentity in ('a', 'd')) THEN 'i' ELSE 'n' END) AS colconstype,
seq.*
FROM pg_attribute att
JOIN pg_type ty ON ty.oid=atttypid
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/update.sql
index 7cd40fc9..58136911 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/update.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/10_plus/update.sql
@@ -33,11 +33,11 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
{% endif %}
{### Alter column - add identity ###}
-{% if 'attidentity' in data and o_data.attidentity == '' and data.attidentity != o_data.attidentity %}
+{% if data.colconstype == 'i' and 'attidentity' in data and o_data.attidentity == '' and data.attidentity != o_data.attidentity %}
ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} {% if data.attidentity == 'a' %}ADD GENERATED ALWAYS AS IDENTITY{% else%}ADD GENERATED BY DEFAULT AS IDENTITY{% endif %}
{% if data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache %} ( {% endif %}
-{% if data.seqincrement is defined and data.seqcycle %}
+{% if data.seqcycle is defined and data.seqcycle %}
CYCLE {% endif %}{% if data.seqincrement is defined and data.seqincrement|int(-1) > -1 %}
INCREMENT {{data.seqincrement|int}} {% endif %}{% if data.seqstart is defined and data.seqstart|int(-1) > -1%}
START {{data.seqstart|int}} {% endif %}{% if data.seqmin is defined and data.seqmin|int(-1) > -1%}
@@ -52,10 +52,8 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} SET GENERATED {% if data.attidentity == 'a' %}ALWAYS{% else%}BY DEFAULT{% endif %};
{% endif %}
-
{### Alter column - change identity - sequence options ###}
{% if 'attidentity' not in data and (data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache) %}
-
ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}} {% else %}{{conn|qtTypeIdent(o_data.name)}} {% endif %}
{% if data.seqcycle %}
@@ -65,11 +63,10 @@ SET START {{data.seqstart|int}} {% endif %}{% if data.seqmin is defined and data
SET MINVALUE {{data.seqmin|int}} {% endif %}{% if data.seqmax is defined and data.seqmax|int(-1) > -1%}
SET MAXVALUE {{data.seqmax|int}} {% endif %}{% if data.seqcache is defined and data.seqcache|int(-1) > -1%}
SET CACHE {{data.seqcache|int}} {% endif %};
-{% endif %}
-
-{### Alter column - drop identity ###}
-{% if 'attidentity' in data and data.attidentity == '' and o_data.attidentity != '' and data.attidentity != o_data.attidentity %}
+{% endif %}
+{### Alter column - drop identity when column constraint is changed###}
+{% if 'colconstype' in data and data.colconstype == 'n' and 'colconstype' in o_data and o_data.colconstype == 'i' %}
ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} DROP IDENTITY;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/create.sql
new file mode 100644
index 00000000..108d3b5e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/create.sql
@@ -0,0 +1,46 @@
+{% import 'columns/macros/security.macros' as SECLABEL %}
+{% import 'columns/macros/privilege.macros' as PRIVILEGE %}
+{% import 'macros/variable.macros' as VARIABLE %}
+{% import 'types/macros/get_full_type_sql_format.macros' as GET_TYPE %}
+{### Add column ###}
+{% if data.name and data.cltype %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
+ ADD COLUMN {{conn|qtIdent(data.name)}} {% if is_sql %}{{data.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, data.cltype, data.attlen, data.attprecision, data.hasSqrBracket) }}{% endif %}{% if data.collspcname %}
+ COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %}
+ NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none and data.defval != '' and data.colconstype != 'g' %}
+ DEFAULT {{data.defval}}{% endif %}{% if data.colconstype == 'i' %}{% if data.attidentity and data.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif data.attidentity and data.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
+{% if data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache %} ( {% endif %}
+{% if data.seqcycle is defined and data.seqcycle %}
+CYCLE {% endif %}{% if data.seqincrement is defined and data.seqincrement|int(-1) > -1 %}
+INCREMENT {{data.seqincrement|int}} {% endif %}{% if data.seqstart is defined and data.seqstart|int(-1) > -1%}
+START {{data.seqstart|int}} {% endif %}{% if data.seqmin is defined and data.seqmin|int(-1) > -1%}
+MINVALUE {{data.seqmin|int}} {% endif %}{% if data.seqmax is defined and data.seqmax|int(-1) > -1%}
+MAXVALUE {{data.seqmax|int}} {% endif %}{% if data.seqcache is defined and data.seqcache|int(-1) > -1%}
+CACHE {{data.seqcache|int}} {% endif %}
+{% if data.seqincrement or data.seqcycle or data.seqincrement or data.seqstart or data.seqmin or data.seqmax or data.seqcache %}){% endif %}
+{% endif %}{% endif %}{% if data.colconstype == 'g' and data.genexpr and data.genexpr != '' %} GENERATED ALWAYS AS ({{data.genexpr}}) STORED{% endif %};
+
+{### Add comments ###}
+{% if data and data.description %}
+COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}}
+ IS {{data.description|qtLiteral}};
+
+{% endif %}
+{### Add variables to column ###}
+{% if data.attoptions %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
+ {{ VARIABLE.SET(conn, 'COLUMN', data.name, data.attoptions) }}
+
+{% endif %}
+{### ACL ###}
+{% if data.attacl %}
+{% for priv in data.attacl %}
+{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+{### Security Lables ###}
+{% if data.seclabels %}
+{% for r in data.seclabels %}
+{{ SECLABEL.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql
new file mode 100644
index 00000000..84b6f374
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql
@@ -0,0 +1,48 @@
+SELECT att.attname as name, att.*, def.*, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval,
+ CASE WHEN att.attndims > 0 THEN 1 ELSE 0 END AS isarray,
+ format_type(ty.oid,NULL) AS typname,
+ format_type(ty.oid,att.atttypmod) AS displaytypname,
+ CASE WHEN ty.typelem > 0 THEN ty.typelem ELSE ty.oid END as elemoid,
+ tn.nspname as typnspname, et.typname as elemtypname,
+ ty.typstorage AS defaultstorage, cl.relname, na.nspname,
+ concat(quote_ident(na.nspname) ,'.', quote_ident(cl.relname)) AS parent_tbl,
+ att.attstattarget, description, cs.relname AS sername,
+ ns.nspname AS serschema,
+ (SELECT count(1) FROM pg_type t2 WHERE t2.typname=ty.typname) > 1 AS isdup,
+ indkey, coll.collname, nspc.nspname as collnspname , attoptions,
+ -- Start pgAdmin4, added to save time on client side parsing
+ CASE WHEN length(coll.collname) > 0 AND length(nspc.nspname) > 0 THEN
+ concat(quote_ident(nspc.nspname),'.',quote_ident(coll.collname))
+ ELSE '' END AS collspcname,
+ format_type(ty.oid,att.atttypmod) AS cltype,
+ -- End pgAdmin4
+ EXISTS(SELECT 1 FROM pg_constraint WHERE conrelid=att.attrelid AND contype='f' AND att.attnum=ANY(conkey)) As is_fk,
+ (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=att.attrelid AND sl1.objsubid=att.attnum) AS seclabels,
+ (CASE WHEN (att.attnum < 1) THEN true ElSE false END) AS is_sys_column,
+ (CASE WHEN (att.attidentity in ('a', 'd')) THEN 'i' WHEN (att.attgenerated in ('s')) THEN 'g' ELSE 'n' END) AS colconstype,
+ (CASE WHEN (att.attgenerated in ('s')) THEN pg_catalog.pg_get_expr(def.adbin, def.adrelid) END) AS genexpr,
+ seq.*
+FROM pg_attribute att
+ JOIN pg_type ty ON ty.oid=atttypid
+ JOIN pg_namespace tn ON tn.oid=ty.typnamespace
+ JOIN pg_class cl ON cl.oid=att.attrelid
+ JOIN pg_namespace na ON na.oid=cl.relnamespace
+ LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem
+ LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum
+ LEFT OUTER JOIN pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass)
+ LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON classid='pg_class'::regclass AND objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum
+ LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace
+ LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary
+ LEFT OUTER JOIN pg_collation coll ON att.attcollation=coll.oid
+ LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid
+ LEFT OUTER JOIN pg_sequence seq ON cs.oid=seq.seqrelid
+WHERE att.attrelid = {{tid}}::oid
+{% if clid %}
+ AND att.attnum = {{clid}}::int
+{% endif %}
+{### To show system objects ###}
+{% if not show_sys_objects %}
+ AND att.attnum > 0
+{% endif %}
+ AND att.attisdropped IS FALSE
+ ORDER BY att.attnum;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/create.sql
index 50a6a5bd..1e2f5cfa 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/create.sql
@@ -7,7 +7,7 @@
ALTER TABLE {{conn|qtIdent(data.schema, data.table)}}
ADD COLUMN {{conn|qtIdent(data.name)}} {% if is_sql %}{{data.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, data.cltype, data.attlen, data.attprecision, data.hasSqrBracket) }}{% endif %}{% if data.collspcname %}
COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %}
- NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none %}
+ NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none and data.defval != '' %}
DEFAULT {{data.defval}}{% endif %};
{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql
index 1f4c8387..8ba1d882 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql
@@ -42,11 +42,11 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %}
{% if c.name and c.cltype %}
- {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %}
-{% if c.attidentity and c.attidentity != '' %}
+ {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}
+{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
-{% if c.seqincrement is defined and c.seqcycle %}
+{% if c.seqcycle is defined and c.seqcycle %}
CYCLE {% endif %}{% if c.seqincrement is defined and c.seqincrement|int(-1) > -1 %}
INCREMENT {{c.seqincrement|int}} {% endif %}{% if c.seqstart is defined and c.seqstart|int(-1) > -1%}
START {{c.seqstart|int}} {% endif %}{% if c.seqmin is defined and c.seqmin|int(-1) > -1%}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql
index 7acbd008..c4ba9921 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql
@@ -46,11 +46,11 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %}
{% if c.name and c.cltype %}
- {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %}
-{% if c.attidentity and c.attidentity != '' %}
+ {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}
+{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
-{% if c.seqincrement is defined and c.seqcycle %}
+{% if c.seqcycle is defined and c.seqcycle %}
CYCLE {% endif %}{% if c.seqincrement is defined and c.seqincrement|int(-1) > -1 %}
INCREMENT {{c.seqincrement|int}} {% endif %}{% if c.seqstart is defined and c.seqstart|int(-1) > -1%}
START {{c.seqstart|int}} {% endif %}{% if c.seqmin is defined and c.seqmin|int(-1) > -1%}
@@ -59,6 +59,7 @@ MAXVALUE {{c.seqmax|int}} {% endif %}{% if c.seqcache is defined and c.seqcache|
CACHE {{c.seqcache|int}} {% endif %}
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %}){% endif %}
{% endif %}
+{% if c.colconstype == 'g' and c.genexpr and c.genexpr != '' %} GENERATED ALWAYS AS ({{c.genexpr}}) STORED{% endif %}
{% if not loop.last %},
{% endif %}
{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql
index 1aa79347..5a551971 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql
@@ -42,7 +42,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %}
{% if c.name and c.cltype %}
- {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %}
+ {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}
{% if not loop.last %},
{% endif %}
{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/gpdb_5.0_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/gpdb_5.0_plus/create.sql
index 57a0d11a..b6ff1f18 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/gpdb_5.0_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/gpdb_5.0_plus/create.sql
@@ -42,7 +42,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %}
{% if c.name and c.cltype %}
- {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %}
+ {% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}
{% if not loop.last %},
{% endif %}
{% endif %}
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 6b97b253..599eaec7 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
@@ -18,6 +18,7 @@ from pgadmin.utils import server_utils as server_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
+from . import utils as tables_utils
class TableAddTestCase(BaseTestGenerator):
@@ -28,7 +29,9 @@ class TableAddTestCase(BaseTestGenerator):
('Create Range partitioned table with 2 partitions',
dict(url='/browser/table/obj/',
server_min_version=100000,
- partition_type='range'
+ partition_type='range',
+ skip_msg='Partitioned table are not supported by '
+ 'PPAS/PG 10.0 and below.'
)
),
('Create Range partitioned table with 1 default and 2'
@@ -36,21 +39,104 @@ class TableAddTestCase(BaseTestGenerator):
dict(url='/browser/table/obj/',
server_min_version=110000,
partition_type='range',
- is_default=True
+ is_default=True,
+ skip_msg='Partitioned table are not supported by '
+ 'PPAS/PG 10.0 and below.'
)
),
('Create List partitioned table with 2 partitions',
dict(url='/browser/table/obj/',
server_min_version=100000,
- partition_type='list'
+ partition_type='list',
+ skip_msg='Partitioned table are not supported by '
+ 'PPAS/PG 10.0 and below.'
)
),
('Create Hash partitioned table with 2 partitions',
dict(url='/browser/table/obj/',
server_min_version=110000,
- partition_type='hash'
+ partition_type='hash',
+ skip_msg='Hash Partition are not supported by '
+ 'PPAS/PG 11.0 and below.'
)
+ ),
+ ('Create Table with Identity columns',
+ dict(url='/browser/table/obj/',
+ server_min_version=100000,
+ skip_msg='Identity columns are not supported by '
+ 'PPAS/PG 10.0 and below.',
+ columns=[{
+ 'name': 'iden_always',
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'i',
+ 'attidentity': 'a',
+ 'seqincrement': 1,
+ 'seqstart': 1,
+ 'seqmin': 1,
+ 'seqmax': 10,
+ 'seqcache': 1,
+ 'seqcycle': True
+ }, {
+ 'name': 'iden_default',
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'i',
+ 'attidentity': 'd',
+ 'seqincrement': 2,
+ 'seqstart': 2,
+ 'seqmin': 2,
+ 'seqmax': 2000,
+ 'seqcache': 1,
+ 'seqcycle': True
+ }])
+ ),
+ ('Create Table with Generated columns',
+ dict(url='/browser/table/obj/',
+ server_min_version=120000,
+ skip_msg='Generated columns are not supported by '
+ 'PPAS/PG 12.0 and below.',
+ columns=[{
+ 'name': 'm1',
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attoptions': [],
+ 'seclabels': []
+ }, {
+ 'name': 'm2',
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attoptions': [],
+ 'seclabels': []
+ }, {
+ 'name': 'genrated',
+ 'cltype': 'bigint',
+ 'attacl': [],
+ 'is_primary_key': False,
+ 'attnotnull': True,
+ 'attlen': None,
+ 'attprecision': None,
+ 'attoptions': [],
+ 'seclabels': [],
+ 'colconstype': 'g',
+ 'genexpr': 'm1*m2'
+ }])
)
+
]
def setUp(self):
@@ -70,145 +156,48 @@ class TableAddTestCase(BaseTestGenerator):
if not schema_response:
raise Exception("Could not find the schema to add a table.")
- self.is_partition = False
if hasattr(self, 'server_min_version'):
server_con = server_utils.connect_server(self, self.server_id)
if not server_con["info"] == "Server connected.":
raise Exception("Could not connect to server to add "
"partitioned table.")
if server_con["data"]["version"] < self.server_min_version:
- message = "Partitioned table are not supported by " \
- "PPAS/PG 10.0 and below."
- self.skipTest(message)
- else:
- self.is_partition = True
+ self.skipTest(self.skip_msg)
def runTest(self):
""" This function will add table under schema node. """
db_user = self.server["username"]
self.table_name = "test_table_add_%s" % (str(uuid.uuid4())[1:8])
- data = {
- "check_constraint": [],
- "coll_inherits": "[]",
- "columns": [
- {
- "name": "empno",
- "cltype": "numeric",
- "attacl": [],
- "is_primary_key": False,
- "attoptions": [],
- "seclabels": []
- },
- {
- "name": "empname",
- "cltype": "character[]",
- "attacl": [],
- "is_primary_key": False,
- "attoptions": [],
- "seclabels": []
- },
- {
- "name": "DOJ",
- "cltype": "date",
- "attacl": [],
- "is_primary_key": False,
- "attoptions": [],
- "seclabels": []
- }
- ],
- "exclude_constraint": [],
- "fillfactor": "",
- "hastoasttable": True,
- "like_constraints": True,
- "like_default_value": True,
- "like_relation": "pg_catalog.pg_namespace",
+ # Get the common data
+ data = tables_utils.get_table_common_data()
+ data.update({
"name": self.table_name,
- "primary_key": [],
- "relacl": [
- {
- "grantee": db_user,
- "grantor": db_user,
- "privileges":
- [
- {
- "privilege_type": "a",
- "privilege": True,
- "with_grant": True
- },
- {
- "privilege_type": "r",
- "privilege": True,
- "with_grant": False
- },
- {
- "privilege_type": "w",
- "privilege": True,
- "with_grant": False
- }
- ]
- }
- ],
- "relhasoids": True,
"relowner": db_user,
"schema": self.schema_name,
- "seclabels": [],
- "spcname": "pg_default",
- "unique_constraint": [],
- "vacuum_table": [
- {
- "name": "autovacuum_analyze_scale_factor"
- },
- {
- "name": "autovacuum_analyze_threshold"
- },
- {
- "name": "autovacuum_freeze_max_age"
- },
- {
- "name": "autovacuum_vacuum_cost_delay"
- },
- {
- "name": "autovacuum_vacuum_cost_limit"
- },
- {
- "name": "autovacuum_vacuum_scale_factor"
- },
- {
- "name": "autovacuum_vacuum_threshold"
- },
- {
- "name": "autovacuum_freeze_min_age"
- },
- {
- "name": "autovacuum_freeze_table_age"
- }
- ],
- "vacuum_toast": [
- {
- "name": "autovacuum_freeze_max_age"
- },
- {
- "name": "autovacuum_vacuum_cost_delay"
- },
- {
- "name": "autovacuum_vacuum_cost_limit"
- },
- {
- "name": "autovacuum_vacuum_scale_factor"
- },
- {
- "name": "autovacuum_vacuum_threshold"
- },
- {
- "name": "autovacuum_freeze_min_age"
- },
- {
- "name": "autovacuum_freeze_table_age"
- }
- ]
- }
+ "relacl": [{
+ "grantee": db_user,
+ "grantor": db_user,
+ "privileges": [{
+ "privilege_type": "a",
+ "privilege": True,
+ "with_grant": True
+ }, {
+ "privilege_type": "r",
+ "privilege": True,
+ "with_grant": False
+ }, {
+ "privilege_type": "w",
+ "privilege": True,
+ "with_grant": False
+ }]
+ }]
+ })
+
+ # If column is provided in the scenario then use those columns
+ if hasattr(self, 'columns'):
+ data['columns'] = self.columns
- if self.is_partition:
+ if hasattr(self, 'partition_type'):
data['partition_type'] = self.partition_type
data['is_partitioned'] = True
if self.partition_type == 'range':
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
index 51dd5d98..148e15cf 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
@@ -242,3 +242,81 @@ def set_partition_data(server, db_name, schema_name, table_name,
}]
}
)
+
+
+def get_table_common_data():
+ """
+ This function will return the common data used to create a table
+ :return:
+ """
+ return {
+ "check_constraint": [],
+ "coll_inherits": "[]",
+ "columns": [{
+ "name": "empno",
+ "cltype": "numeric",
+ "attacl": [],
+ "is_primary_key": False,
+ "attoptions": [],
+ "seclabels": []
+ }, {
+ "name": "empname",
+ "cltype": "character[]",
+ "attacl": [],
+ "is_primary_key": False,
+ "attoptions": [],
+ "seclabels": []
+ }, {
+ "name": "DOJ",
+ "cltype": "date",
+ "attacl": [],
+ "is_primary_key": False,
+ "attoptions": [],
+ "seclabels": []
+ }],
+ "exclude_constraint": [],
+ "fillfactor": "",
+ "hastoasttable": True,
+ "like_constraints": True,
+ "like_default_value": True,
+ "like_relation": "pg_catalog.pg_namespace",
+ "primary_key": [],
+ "relhasoids": True,
+ "seclabels": [],
+ "spcname": "pg_default",
+ "unique_constraint": [],
+ "vacuum_table": [{
+ "name": "autovacuum_analyze_scale_factor"
+ }, {
+ "name": "autovacuum_analyze_threshold"
+ }, {
+ "name": "autovacuum_freeze_max_age"
+ }, {
+ "name": "autovacuum_vacuum_cost_delay"
+ }, {
+ "name": "autovacuum_vacuum_cost_limit"
+ }, {
+ "name": "autovacuum_vacuum_scale_factor"
+ }, {
+ "name": "autovacuum_vacuum_threshold"
+ }, {
+ "name": "autovacuum_freeze_min_age"
+ }, {
+ "name": "autovacuum_freeze_table_age"
+ }],
+ "vacuum_toast": [{
+ "name": "autovacuum_freeze_max_age"
+ }, {
+ "name": "autovacuum_vacuum_cost_delay"
+ }, {
+ "name": "autovacuum_vacuum_cost_limit"
+ }, {
+ "name": "autovacuum_vacuum_scale_factor"
+ }, {
+ "name": "autovacuum_vacuum_threshold"
+ }, {
+ "name": "autovacuum_freeze_min_age"
+ }, {
+ "name": "autovacuum_freeze_table_age"
+ }]
+ }
diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js
index 012a7432..8e69cfb8 100644
--- a/web/pgadmin/static/js/backform.pgadmin.js
+++ b/web/pgadmin/static/js/backform.pgadmin.js
@@ -448,11 +448,11 @@ define([
template: _.template([
'<label class="<%=controlLabelClassName%>"><%=label%></label>',
'<div class="<%=controlsClassName%> <%=extraClasses.join(\' \')%>">',
- ' <div class="btn-group pgadmin-controls-radio-none" data-toggle="buttons">',
+ ' <div class="btn-group pgadmin-controls-radio-none<% if (disabled) {%> disabled <%}%>" data-toggle="buttons">',
' <% for (var i=0; i < options.length; i++) { %>',
' <% var option = options[i]; %>',
- ' <label class="btn btn-primary<% if (option.value == value) { %> active<%}%>" tabindex="0">',
- ' <input type="radio" name="<%=name%>" autocomplete="off" value=<%-formatter.fromRaw(option.value)%> <% if (option.value == value) { %> checked<%}%> > <%-option.label%>',
+ ' <label class="btn btn-primary<% if (option.value == value) { %> active<%}%><% if (!option.disabled && !disabled) { %>" tabindex="0"<% } else { %> disabled"<% } %>>',
+ ' <input type="radio" name="<%=name%>" autocomplete="off" value=<%-formatter.fromRaw(option.value)%> <% if (option.value == value) { %> checked<%}%> <% if (option.disabled || disabled) { %> disabled <%}%>> <%-option.label%>',
' </label>',
' <% } %>',
' </div>',
@@ -466,7 +466,37 @@ define([
return this.formatter.toRaw(this.$el.find('input[type="radio"]:checked').attr('value'), this.model);
},
render: function() {
- Backform.RadioControl.prototype.render.apply(this, arguments);
+ var field = _.defaults(this.field.toJSON(), this.defaults),
+ attributes = this.model.toJSON(),
+ attrArr = field.name.split('.'),
+ name = attrArr.shift(),
+ path = attrArr.join('.'),
+ rawValue = this.keyPathAccessor(attributes[name], path),
+ data = _.extend(field, {
+ rawValue: rawValue,
+ value: this.formatter.fromRaw(rawValue, this.model),
+ attributes: attributes,
+ formatter: this.formatter,
+ }),
+ // Evaluate the disabled, visible, and required option
+ evalF = function evalF(f, d, m) {
+ return _.isFunction(f) ? !!f.apply(d, [m]) : !!f;
+ };
+
+ _.extend(data, {
+ disabled: evalF(data.disabled, data, this.model),
+ visible: evalF(data.visible, data, this.model),
+ required: evalF(data.required, data, this.model),
+ }); // Clean up first
+
+ data.options = _.isFunction(data.options) ?
+ data.options.apply(data, [this.model]) : data.options;
+
+ this.$el.removeClass(Backform.hiddenClassName);
+ if (!data.visible) this.$el.addClass(Backform.hiddenClassName);
+ this.$el.html(this.template(data)).addClass(field.name);
+ this.updateInvalid();
+
this.$el.find('.btn').on('keyup', (e)=>{
switch(e.keyCode) {
case 32: /* Spacebar click */
diff --git a/web/pgadmin/static/scss/_bootstrap.overrides.scss b/web/pgadmin/static/scss/_bootstrap.overrides.scss
index 55fc5afa..3b2df9fd 100644
--- a/web/pgadmin/static/scss/_bootstrap.overrides.scss
+++ b/web/pgadmin/static/scss/_bootstrap.overrides.scss
@@ -315,3 +315,11 @@ td.switch-cell > div.toggle {
background-color: $color-primary-light;
color: $color-primary;
}
+
+.btn-group.pgadmin-controls-radio-none.disabled {
+ pointer-events: none;
+}
+
+.btn-group.pgadmin-controls-radio-none > label.btn.btn-primary.disabled {
+ pointer-events: none;
+}
view thread (3+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [pgAdmin4][Patch]: Fix for Feature #4334, RM #4496 and #4497
In-Reply-To: <CANxoLDcbVUZUjQiS56g07EHaR3+5340P9QjpES2GC_JoRd5+gg@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox