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
@ -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.
|
||||
|
||||
|
BIN
docs/en_US/images/column_constraint_generated.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/en_US/images/column_constraint_identity.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
docs/en_US/images/column_constraints.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/en_US/images/column_definition.png
Executable file → Normal file
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 43 KiB |
BIN
docs/en_US/images/column_general.png
Executable file → Normal file
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 37 KiB |
BIN
docs/en_US/images/column_security.png
Executable file → Normal file
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 42 KiB |
BIN
docs/en_US/images/column_variables.png
Executable file → Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 40 KiB |
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -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,11 +749,22 @@ 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') &&
|
||||
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, '') == '') {
|
||||
@ -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)
|
||||
|
@ -25,11 +25,33 @@ 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/',
|
||||
('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,
|
||||
identity_opt={
|
||||
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,
|
||||
@ -38,10 +60,22 @@ class ColumnAddTestCase(BaseTestGenerator):
|
||||
'seqcache': 1,
|
||||
'seqcycle': True
|
||||
})),
|
||||
('Add column with Identity', dict(url='/browser/column/obj/',
|
||||
('Add Identity column with As Default', dict(
|
||||
url='/browser/column/obj/',
|
||||
col_data_type='bigint',
|
||||
server_min_version=100000,
|
||||
identity_opt={
|
||||
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,
|
||||
@ -49,7 +83,25 @@ class ColumnAddTestCase(BaseTestGenerator):
|
||||
'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)
|
||||
|
||||
|
@ -26,12 +26,21 @@ 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/',
|
||||
('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,
|
||||
identity_opt={
|
||||
skip_msg='Identity column are not supported by EPAS/PG 10.0 '
|
||||
'and below.',
|
||||
data={
|
||||
'attnotnull': True,
|
||||
'attidentity': 'a',
|
||||
'seqincrement': 1,
|
||||
'seqstart': 1,
|
||||
@ -40,10 +49,14 @@ class ColumnPutTestCase(BaseTestGenerator):
|
||||
'seqcache': 1,
|
||||
'seqcycle': True
|
||||
})),
|
||||
('Edit column with Identity', dict(url='/browser/column/obj/',
|
||||
server_min_version=100000,
|
||||
('Edit column to Identity column as Default', dict(
|
||||
url='/browser/column/obj/',
|
||||
col_data_type='bigint',
|
||||
identity_opt={
|
||||
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,
|
||||
@ -51,7 +64,16 @@ class ColumnPutTestCase(BaseTestGenerator):
|
||||
'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)
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 %}
|
@ -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;
|
@ -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 %}
|
||||
|
@ -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%}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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": [
|
||||
{
|
||||
"relowner": db_user,
|
||||
"schema": self.schema_name,
|
||||
"relacl": [{
|
||||
"grantee": db_user,
|
||||
"grantor": db_user,
|
||||
"privileges":
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
})
|
||||
|
||||
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':
|
||||
|
@ -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"
|
||||
}]
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|