mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added option to create unique constraint with nulls not distinct. #5855
This commit is contained in:
parent
3de2e625b5
commit
89c8a7f907
@ -151,10 +151,7 @@ export default class PrimaryKeySchema extends BaseUISchema {
|
|||||||
editable: false,
|
editable: false,
|
||||||
canDelete: true, canAdd: true,
|
canDelete: true, canAdd: true,
|
||||||
mode: ['properties', 'create', 'edit'],
|
mode: ['properties', 'create', 'edit'],
|
||||||
visible: function() {
|
min_version: 110000,
|
||||||
/* In table properties, nodeInfo is not available */
|
|
||||||
return this.getServerVersion() >= 110000;
|
|
||||||
},
|
|
||||||
deps: ['index'],
|
deps: ['index'],
|
||||||
readonly: function(state) {
|
readonly: function(state) {
|
||||||
return obj.isReadOnly(state);
|
return obj.isReadOnly(state);
|
||||||
|
@ -25,6 +25,7 @@ export default class UniqueConstraintSchema extends BaseUISchema {
|
|||||||
fillfactor: undefined,
|
fillfactor: undefined,
|
||||||
condeferrable: undefined,
|
condeferrable: undefined,
|
||||||
condeferred: undefined,
|
condeferred: undefined,
|
||||||
|
indnullsnotdistinct: undefined,
|
||||||
columns: [],
|
columns: [],
|
||||||
include: [],
|
include: [],
|
||||||
});
|
});
|
||||||
@ -150,12 +151,7 @@ export default class UniqueConstraintSchema extends BaseUISchema {
|
|||||||
editable: false,
|
editable: false,
|
||||||
canDelete: true, canAdd: true,
|
canDelete: true, canAdd: true,
|
||||||
mode: ['properties', 'create', 'edit'],
|
mode: ['properties', 'create', 'edit'],
|
||||||
visible: function() {
|
min_version: 110000,
|
||||||
/* 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);
|
|
||||||
},
|
|
||||||
deps: ['index'],
|
deps: ['index'],
|
||||||
readonly: function(state) {
|
readonly: function(state) {
|
||||||
return obj.isReadOnly(state);
|
return obj.isReadOnly(state);
|
||||||
@ -256,6 +252,13 @@ export default class UniqueConstraintSchema extends BaseUISchema {
|
|||||||
return {condeferred: false};
|
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
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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';
|
@ -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';
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -173,6 +173,28 @@
|
|||||||
"error_msg": "Mocked Internal Server Error",
|
"error_msg": "Mocked Internal Server Error",
|
||||||
"test_result_data": {}
|
"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": [
|
"index_constraint_delete": [
|
||||||
|
@ -21,6 +21,7 @@ from pgadmin.utils.route import BaseTestGenerator
|
|||||||
from regression import parent_node_dict
|
from regression import parent_node_dict
|
||||||
from regression.python_test_utils import test_utils as utils
|
from regression.python_test_utils import test_utils as utils
|
||||||
from . import utils as index_constraint_utils
|
from . import utils as index_constraint_utils
|
||||||
|
from pgadmin.utils import server_utils
|
||||||
|
|
||||||
|
|
||||||
class IndexConstraintAddTestCase(BaseTestGenerator):
|
class IndexConstraintAddTestCase(BaseTestGenerator):
|
||||||
@ -35,10 +36,21 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
|
|||||||
# Load test data
|
# Load test data
|
||||||
self.data = self.test_data
|
self.data = self.test_data
|
||||||
|
|
||||||
# Create db connection
|
# Check server version
|
||||||
self.db_name = parent_node_dict["database"][-1]["db_name"]
|
|
||||||
schema_info = parent_node_dict["schema"][-1]
|
schema_info = parent_node_dict["schema"][-1]
|
||||||
self.server_id = schema_info["server_id"]
|
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"]
|
self.db_id = schema_info["db_id"]
|
||||||
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
|
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
|
||||||
self.server_id, self.db_id)
|
self.server_id, self.db_id)
|
||||||
|
@ -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 %}
|
@ -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
|
@ -25,7 +25,7 @@
|
|||||||
{% for data in unique_data %}
|
{% for data in unique_data %}
|
||||||
{% if data.columns|length > 0 %}{% if loop.index !=1 %},{% endif %}
|
{% 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 %}
|
{% 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 %}
|
INCLUDE({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}){% endif %}
|
||||||
|
@ -101,11 +101,7 @@ export default class SubscriptionSchema extends BaseUISchema{
|
|||||||
return [{
|
return [{
|
||||||
id: 'name', label: gettext('Name'), type: 'text',
|
id: 'name', label: gettext('Name'), type: 'text',
|
||||||
mode: ['properties', 'create', 'edit'], noEmpty: true,
|
mode: ['properties', 'create', 'edit'], noEmpty: true,
|
||||||
visible: function() {
|
min_version: 100000
|
||||||
return (!_.isUndefined(this.node_info['node_info'])
|
|
||||||
&& !_.isUndefined(this.node_info['node_info'].version)
|
|
||||||
&& this.node_info['node_info'].version >= 100000);
|
|
||||||
},
|
|
||||||
},{
|
},{
|
||||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
@ -149,7 +149,6 @@ export class SaveOptSchema extends BaseUISchema {
|
|||||||
|
|
||||||
|
|
||||||
get baseFields() {
|
get baseFields() {
|
||||||
let obj = this;
|
|
||||||
return [{
|
return [{
|
||||||
id: 'dns_owner',
|
id: 'dns_owner',
|
||||||
label: gettext('Owner'),
|
label: gettext('Owner'),
|
||||||
@ -180,11 +179,7 @@ export class SaveOptSchema extends BaseUISchema {
|
|||||||
type: 'switch',
|
type: 'switch',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
group: gettext('Do not save'),
|
group: gettext('Do not save'),
|
||||||
visible: function() {
|
min_version: 110000
|
||||||
let serverInfo = _.isUndefined(obj.fieldOptions.nodeInfo) ? undefined : obj.fieldOptions.nodeInfo.server;
|
|
||||||
|
|
||||||
return _.isUndefined(serverInfo) ? false : serverInfo.version >= 110000;
|
|
||||||
},
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +213,6 @@ export class QueryOptionSchema extends BaseUISchema {
|
|||||||
|
|
||||||
|
|
||||||
get baseFields() {
|
get baseFields() {
|
||||||
let obj = this;
|
|
||||||
return [{
|
return [{
|
||||||
id: 'use_column_inserts',
|
id: 'use_column_inserts',
|
||||||
label: gettext('Use Column Inserts'),
|
label: gettext('Use Column Inserts'),
|
||||||
@ -237,9 +231,7 @@ export class QueryOptionSchema extends BaseUISchema {
|
|||||||
type: 'switch',
|
type: 'switch',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
group: gettext('Queries'),
|
group: gettext('Queries'),
|
||||||
visible: function() {
|
visible: isVisible,
|
||||||
return !(!_.isUndefined(obj.backupType) && obj.backupType === 'server');
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
id: 'include_drop_database',
|
id: 'include_drop_database',
|
||||||
label: gettext('Include DROP DATABASE statement'),
|
label: gettext('Include DROP DATABASE statement'),
|
||||||
@ -259,14 +251,8 @@ export class QueryOptionSchema extends BaseUISchema {
|
|||||||
type: 'switch',
|
type: 'switch',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
group: gettext('Queries'),
|
group: gettext('Queries'),
|
||||||
visible: function() {
|
min_version: 110000,
|
||||||
if (!_.isUndefined(obj.backupType) && obj.backupType === 'server')
|
visible: isVisible,
|
||||||
return false;
|
|
||||||
|
|
||||||
let serverInfo = _.isUndefined(obj.fieldOptions.nodeInfo) ? undefined : obj.fieldOptions.nodeInfo.server;
|
|
||||||
|
|
||||||
return _.isUndefined(serverInfo) ? false : serverInfo.version >= 110000;
|
|
||||||
},
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,13 +460,8 @@ export default class BackupSchema extends BaseUISchema {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
options: obj.fieldOptions.encoding,
|
options: obj.fieldOptions.encoding,
|
||||||
visible: function() {
|
min_version: 110000,
|
||||||
if (!_.isUndefined(obj.backupType) && obj.backupType === 'server') {
|
visible: isVisible
|
||||||
let dbNode = obj.pgBrowser.serverInfo[obj.treeNodeInfo.server._id];
|
|
||||||
return _.isUndefined(dbNode) ? false : dbNode.version >= 110000;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
id: 'no_of_jobs',
|
id: 'no_of_jobs',
|
||||||
label: gettext('Number of jobs'),
|
label: gettext('Number of jobs'),
|
||||||
|
@ -151,7 +151,6 @@ export class RestoreSaveOptSchema extends BaseUISchema {
|
|||||||
|
|
||||||
|
|
||||||
get baseFields() {
|
get baseFields() {
|
||||||
let obj = this;
|
|
||||||
return [{
|
return [{
|
||||||
id: 'dns_owner',
|
id: 'dns_owner',
|
||||||
label: gettext('Owner'),
|
label: gettext('Owner'),
|
||||||
@ -176,10 +175,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
|
|||||||
type: 'switch',
|
type: 'switch',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
group: gettext('Do not save'),
|
group: gettext('Do not save'),
|
||||||
visible: function() {
|
min_version: 110000
|
||||||
let serverInfo = obj.fieldOptions.nodeInfo.server;
|
|
||||||
return !_.isUndefined(serverInfo) && serverInfo.version >= 110000 ? true : false;
|
|
||||||
},
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user