Added option to create unique constraint with nulls not distinct. #5855

This commit is contained in:
Pravesh Sharma 2023-03-15 18:55:42 +05:30 committed by GitHub
parent 3de2e625b5
commit 89c8a7f907
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 204 additions and 48 deletions

View File

@ -151,10 +151,7 @@ export default class PrimaryKeySchema extends BaseUISchema {
editable: false,
canDelete: true, canAdd: true,
mode: ['properties', 'create', 'edit'],
visible: function() {
/* In table properties, nodeInfo is not available */
return this.getServerVersion() >= 110000;
},
min_version: 110000,
deps: ['index'],
readonly: function(state) {
return obj.isReadOnly(state);

View File

@ -25,6 +25,7 @@ export default class UniqueConstraintSchema extends BaseUISchema {
fillfactor: undefined,
condeferrable: undefined,
condeferred: undefined,
indnullsnotdistinct: undefined,
columns: [],
include: [],
});
@ -150,12 +151,7 @@ export default class UniqueConstraintSchema extends BaseUISchema {
editable: false,
canDelete: true, canAdd: true,
mode: ['properties', 'create', 'edit'],
visible: function() {
/* In table properties, nodeInfo is not available */
return (!_.isUndefined(this.nodeInfo) && !_.isUndefined(this.nodeInfo.server)
&& !_.isUndefined(this.nodeInfo.server.version) &&
this.nodeInfo.server.version >= 110000);
},
min_version: 110000,
deps: ['index'],
readonly: function(state) {
return obj.isReadOnly(state);
@ -256,6 +252,13 @@ export default class UniqueConstraintSchema extends BaseUISchema {
return {condeferred: false};
}
}
},{
id: 'indnullsnotdistinct', label: gettext('NULLs not distinct?'),
type: 'switch', group: gettext('Definition'),
readonly: function(state) {
return obj.isReadOnly(state);
},
min_version: 150000
}];
}

View File

@ -0,0 +1,12 @@
-- Constraint: UC_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE IF EXISTS testschema.tablefor_unique_cons DROP CONSTRAINT IF EXISTS "UC_$%{}[]()&*^!@""'`\/#";
ALTER TABLE IF EXISTS testschema.tablefor_unique_cons
ADD CONSTRAINT "UC_$%{}[]()&*^!@""'`\/#" UNIQUE NULLS NOT DISTINCT (col1)
INCLUDE (col2)
WITH (FILLFACTOR=20)
DEFERRABLE INITIALLY DEFERRED;
COMMENT ON CONSTRAINT "UC_$%{}[]()&*^!@""'`\/#" ON testschema.tablefor_unique_cons
IS 'Comment for create';

View File

@ -0,0 +1,8 @@
ALTER TABLE IF EXISTS testschema.tablefor_unique_cons
ADD CONSTRAINT "UC_$%{}[]()&*^!@""'`\/#" UNIQUE NULLS NOT DISTINCT (col1)
INCLUDE (col2)
WITH (FILLFACTOR=20)
DEFERRABLE INITIALLY DEFERRED;
COMMENT ON CONSTRAINT "UC_$%{}[]()&*^!@""'`\/#" ON testschema.tablefor_unique_cons
IS 'Comment for create';

View File

@ -0,0 +1,77 @@
{
"scenarios": [
{
"type": "create",
"name": "Create Table",
"endpoint": "NODE-table.obj",
"sql_endpoint": "NODE-table.sql_id",
"data": {
"name": "tablefor_unique_cons",
"columns": [{
"name": "col1",
"cltype": "integer",
"is_primary_key": false
}, {
"name": "col2",
"cltype": "integer",
"is_primary_key": false
}],
"is_partitioned": false,
"schema": "testschema",
"spcname": "pg_default"
},
"store_object_id": true
}, {
"type": "create",
"name": "Create Index",
"endpoint": "NODE-index.obj",
"sql_endpoint": "NODE-index.sql_id",
"data": {
"name": "uindex",
"spcname": "pg_default",
"amname": "btree",
"columns": [{
"colname": "col1",
"sort_order": false,
"nulls": false,
"is_sort_nulls_applicable": true
}],
"indisunique": true,
"fillfactor": 20
}
}, {
"type": "create",
"name": "Create Unique Constraint -- 15 Plus",
"endpoint": "NODE-unique_constraint.obj",
"sql_endpoint": "NODE-unique_constraint.sql_id",
"msql_endpoint": "NODE-unique_constraint.msql",
"data": {
"name": "UC_$%{}[]()&*^!@\"'`\\/#",
"comment": "Comment for create",
"fillfactor": 20,
"columns": [{"column":"col1"}],
"include": ["col2"],
"condeferrable": true,
"condeferred": true,
"indnullsnotdistinct": true
},
"expected_sql_file": "create_unique_constraint.sql",
"expected_msql_file": "create_unique_constraint_msql.sql"
}, {
"type": "delete",
"name": "Drop Unique Constraint -- 15 Plus",
"endpoint": "NODE-unique_constraint.delete_id",
"data": {
"name": "UC_$%{}[]()&*^!@\"'`\\/#a"
}
}, {
"type": "delete",
"name": "Drop Unique Constraint Table -- 15 Plus",
"endpoint": "NODE-table.delete_id",
"data": {
"name": "tablefor_unique_cons"
}
}
]
}

View File

@ -173,6 +173,28 @@
"error_msg": "Mocked Internal Server Error",
"test_result_data": {}
}
},
{
"name": "Create: Add unique key constraint to table with nulls not distinct.",
"url": "/browser/unique_constraint/obj/",
"is_positive_test": true,
"inventory_data": {
"server_min_version": 150000,
"skip_msg": "Nulls not distinct is not supported by PPAS/PG 15.0 and below."
},
"test_data": {
"constraint_name": "test_uniquekey_add_",
"spcname": "pg_default",
"columns": [{"column": "id"}],
"indnullsnotdistinct": true
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200,
"error_msg": null,
"test_result_data": {}
}
}
],
"index_constraint_delete": [

View File

@ -21,6 +21,7 @@ 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 index_constraint_utils
from pgadmin.utils import server_utils
class IndexConstraintAddTestCase(BaseTestGenerator):
@ -35,10 +36,21 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
# Load test data
self.data = self.test_data
# Create db connection
self.db_name = parent_node_dict["database"][-1]["db_name"]
# Check server version
schema_info = parent_node_dict["schema"][-1]
self.server_id = schema_info["server_id"]
if "server_min_version" in self.inventory_data:
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.inventory_data["server_min_version"]:
self.skipTest(self.inventory_data["skip_msg"])
# Create db connection
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.db_id = schema_info["db_id"]
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.server_id, self.db_id)

View File

@ -0,0 +1,20 @@
ALTER TABLE IF EXISTS {{ conn|qtIdent(data.schema, data.table) }}
ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} {{constraint_name}}{% if data.indnullsnotdistinct %} NULLS NOT DISTINCT{% endif %} {% if data.index %}USING INDEX {{ conn|qtIdent(data.index) }}{% else %}
({% for columnobj in data.columns %}{% if loop.index != 1 %}
, {% endif %}{{ conn|qtIdent(columnobj.column)}}{% endfor %}){% if data.include|length > 0 %}
INCLUDE ({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}){% endif %}
{% if data.fillfactor %}
WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}{% endif %}{% if data.condeferrable %}
DEFERRABLE{% if data.condeferred %}
INITIALLY DEFERRED{% endif%}
{% endif -%};
{% if data.comment and data.name %}
COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }}
IS {{ data.comment|qtLiteral(conn) }};
{% endif %}

View File

@ -0,0 +1,32 @@
SELECT cls.oid,
cls.relname as name,
indnkeyatts as col_count,
indnullsnotdistinct,
CASE WHEN length(spcname::text) > 0 THEN spcname ELSE
(SELECT sp.spcname FROM pg_catalog.pg_database dtb
JOIN pg_catalog.pg_tablespace sp ON dtb.dattablespace=sp.oid
WHERE dtb.oid = {{ did }}::oid)
END as spcname,
CASE contype
WHEN 'p' THEN desp.description
WHEN 'u' THEN desp.description
WHEN 'x' THEN desp.description
ELSE des.description
END AS comment,
condeferrable,
condeferred,
conislocal,
substring(pg_catalog.array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor
FROM pg_catalog.pg_index idx
JOIN pg_catalog.pg_class cls ON cls.oid=indexrelid
LEFT OUTER JOIN pg_catalog.pg_tablespace ta on ta.oid=cls.reltablespace
LEFT JOIN pg_catalog.pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='pg_constraint') AND dep.deptype='i')
LEFT OUTER JOIN pg_catalog.pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid)
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass)
LEFT OUTER JOIN pg_catalog.pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass)
WHERE indrelid = {{tid}}::oid
{% if cid %}
AND cls.oid = {{cid}}::oid
{% endif %}
AND contype='{{constraint_type}}'
ORDER BY cls.relname

View File

@ -25,7 +25,7 @@
{% for data in unique_data %}
{% if data.columns|length > 0 %}{% if loop.index !=1 %},{% endif %}
{% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}UNIQUE ({% for c in data.columns%}
{% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}UNIQUE {% if data.indnullsnotdistinct %}NULLS NOT DISTINCT {% endif %}({% for c in data.columns%}
{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.column)}}{% endfor %}){% if data.include|length > 0 %}
INCLUDE({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}){% endif %}

View File

@ -101,11 +101,7 @@ export default class SubscriptionSchema extends BaseUISchema{
return [{
id: 'name', label: gettext('Name'), type: 'text',
mode: ['properties', 'create', 'edit'], noEmpty: true,
visible: function() {
return (!_.isUndefined(this.node_info['node_info'])
&& !_.isUndefined(this.node_info['node_info'].version)
&& this.node_info['node_info'].version >= 100000);
},
min_version: 100000
},{
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
type: 'text',

View File

@ -149,7 +149,6 @@ export class SaveOptSchema extends BaseUISchema {
get baseFields() {
let obj = this;
return [{
id: 'dns_owner',
label: gettext('Owner'),
@ -180,11 +179,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
visible: function() {
let serverInfo = _.isUndefined(obj.fieldOptions.nodeInfo) ? undefined : obj.fieldOptions.nodeInfo.server;
return _.isUndefined(serverInfo) ? false : serverInfo.version >= 110000;
},
min_version: 110000
}];
}
}
@ -218,7 +213,6 @@ export class QueryOptionSchema extends BaseUISchema {
get baseFields() {
let obj = this;
return [{
id: 'use_column_inserts',
label: gettext('Use Column Inserts'),
@ -237,9 +231,7 @@ export class QueryOptionSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Queries'),
visible: function() {
return !(!_.isUndefined(obj.backupType) && obj.backupType === 'server');
},
visible: isVisible,
}, {
id: 'include_drop_database',
label: gettext('Include DROP DATABASE statement'),
@ -259,14 +251,8 @@ export class QueryOptionSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Queries'),
visible: function() {
if (!_.isUndefined(obj.backupType) && obj.backupType === 'server')
return false;
let serverInfo = _.isUndefined(obj.fieldOptions.nodeInfo) ? undefined : obj.fieldOptions.nodeInfo.server;
return _.isUndefined(serverInfo) ? false : serverInfo.version >= 110000;
},
min_version: 110000,
visible: isVisible,
}];
}
}
@ -474,13 +460,8 @@ export default class BackupSchema extends BaseUISchema {
type: 'select',
disabled: false,
options: obj.fieldOptions.encoding,
visible: function() {
if (!_.isUndefined(obj.backupType) && obj.backupType === 'server') {
let dbNode = obj.pgBrowser.serverInfo[obj.treeNodeInfo.server._id];
return _.isUndefined(dbNode) ? false : dbNode.version >= 110000;
}
return true;
},
min_version: 110000,
visible: isVisible
}, {
id: 'no_of_jobs',
label: gettext('Number of jobs'),

View File

@ -151,7 +151,6 @@ export class RestoreSaveOptSchema extends BaseUISchema {
get baseFields() {
let obj = this;
return [{
id: 'dns_owner',
label: gettext('Owner'),
@ -176,10 +175,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
visible: function() {
let serverInfo = obj.fieldOptions.nodeInfo.server;
return !_.isUndefined(serverInfo) && serverInfo.version >= 110000 ? true : false;
},
min_version: 110000
}];
}
}