Add support for generated columns in Postgres 12+. Fixes #4334

Ensure columns can be created when they are IDENTITY fields with the CYCLE option enabled. Fixes #4496
Ensure purely numeric comments can be saved on new columns. Fixed #4497
This commit is contained in:
Akshay Joshi 2019-07-25 16:38:26 +01:00 committed by Dave Page
parent 5b322d94e8
commit 2ef3080d0e
28 changed files with 793 additions and 305 deletions

View File

@ -39,9 +39,69 @@ are disabled if inapplicable.)
a text value.
* Use the drop-down listbox next to *Collation* to apply a collation setting to
the column.
Click the *Constraints* tab to continue.
.. image:: images/column_constraints.png
:alt: Column dialog constraints tab
:align: center
Use the fields in the *Constraints* tab to specify constraints for the column.
(Fields are disabled if inapplicable.)
* Use the *Default Value* field to specify a default data value.
* Move the *Not Null* switch to the *Yes* position to specify the column may not
contain null values. The default is *No*.
* Use the *Type* field to specify the column type (NONE/IDENTITY/GENERATED).
The default is *NONE*.
Click the *IDENTITY* type to create Identity column.
.. image:: images/column_constraint_identity.png
:alt: Column dialog constraints tab
:align: center
Use the following fields to create *IDENTITY* column. Identity columns are
applicable for PG/EPAS version 10 and above.
* Use the *Identity* field to specify ALWAYS or BY DEFAULT. This clause is
used to determine how the sequence value is given precedence over a
user-specified value in an INSERT statement.
* Use the *Increment* field to specify which value is added to the current
sequence value to create a new value.
* Provide a value in the *Start* field to specify the beginning value of the
sequence. The default starting value is MINVALUE for ascending sequences and
MAXVALUE for descending ones.
* Provide a value in the *Minimum* field to specify the minimum value a sequence
can generate. If this clause is not supplied or NO MINVALUE is specified,
then defaults will be used. The defaults are 1 and -263-1 for ascending and
descending sequences, respectively.
* Provide a value in the *Maximum* field to specify the maximum value for the
sequence. If this clause is not supplied or NO MAXVALUE is specified, then
default values will be used. The defaults are 263-1 and -1 for ascending and
descending sequences, respectively.
* Provide a value in the *Cache* field to specify how many sequence numbers are
to be preallocated and stored in memory for faster access. The minimum value
is 1 (only one value can be generated at a time, i.e., no cache), and this is
also the default.
* Move the *Cycled* switch to the *Yes* position to allow the sequence to wrap
around when the MAXVALUE or the MINVALUE has been reached by an ascending or
descending sequence respectively. If the limit is reached, the next number
generated will be the MINVALUE or MAXVALUE, respectively. The default is *No*.
Click the *GENERATED* type to create Generated column.
.. image:: images/column_constraint_generated.png
:alt: Column dialog constraints tab
:align: center
Use the following fields to create *GENERATED* column. Generated columns are
applicable for PG/EPAS version 12 and above.
* Use the *Expression* field to specify the generation expression. It can
refer to other columns in the table, but not other generated columns.
Any functions and operators used must be immutable. References to other
tables are not allowed.
Click the *Variables* tab to continue.

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 43 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 37 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 42 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -10,6 +10,7 @@ New features
************
| `Issue #4333 <https://redmine.postgresql.org/issues/4333>`_ - Add support for planner support functions in PostgreSQL 12+ functions.
| `Issue #4334 <https://redmine.postgresql.org/issues/4334>`_ - Add support for generated columns in Postgres 12+.
Housekeeping
************
@ -20,4 +21,6 @@ Bug fixes
| `Issue #4179 <https://redmine.postgresql.org/issues/4179>`_ - Fix generation of reverse engineered SQL for tables with Greenplum 5.x.
| `Issue #4490 <https://redmine.postgresql.org/issues/4490>`_ - Fix accessibility issue for checkbox in IE11.
| `Issue #4496 <https://redmine.postgresql.org/issues/4496>`_ - Ensure columns can be created when they are IDENTITY fields with the CYCLE option enabled.
| `Issue #4497 <https://redmine.postgresql.org/issues/4497>`_ - Ensure purely numeric comments can be saved on new columns.
| `Issue #4508 <https://redmine.postgresql.org/issues/4508>`_ - Fix accessibility issue for Datetime cell in backgrid.

View File

@ -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',

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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 %}

View File

@ -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

View File

@ -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 %}
{### 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;

View File

@ -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 %}

View File

@ -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;

View File

@ -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 %}

View File

@ -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%}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 self.is_partition:
# If column is provided in the scenario then use those columns
if hasattr(self, 'columns'):
data['columns'] = self.columns
if hasattr(self, 'partition_type'):
data['partition_type'] = self.partition_type
data['is_partitioned'] = True
if self.partition_type == 'range':

View File

@ -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"
}]
}

View File

@ -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 */

View File

@ -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;
}