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

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