diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py index 74d1693c..aa88cca8 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py @@ -301,7 +301,7 @@ class ExclusionConstraintView(PGChildNodeView): sql = render_template( "/".join([self.template_path, 'get_constraint_cols.sql']), cid=exid, - colcnt=result['indnatts']) + colcnt=result['col_count']) status, res = self.conn.execute_dict(sql) if not status: @@ -326,6 +326,18 @@ class ExclusionConstraintView(PGChildNodeView): result['columns'] = columns + # Add Include details of the index supported for PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join([self.template_path, 'get_constraint_include.sql']), + cid=exid) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result['include'] = [col['colname'] for col in res['rows']] + return ajax_response( response=result, status=200 @@ -875,7 +887,7 @@ class ExclusionConstraintView(PGChildNodeView): sql = render_template( "/".join([self.template_path, 'get_constraint_cols.sql']), cid=exid, - colcnt=data['indnatts']) + colcnt=data['col_count']) status, res = self.conn.execute_dict(sql) if not status: @@ -899,6 +911,20 @@ class ExclusionConstraintView(PGChildNodeView): data['columns'] = columns + # Add Include details of the index supported for PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join( + [self.template_path, 'get_constraint_include.sql'] + ), + cid=exid) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + data['include'] = [col['colname'] for col in res['rows']] + if not data['amname'] or data['amname'] == '': data['amname'] = 'btree' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js index 8b097034..1befc999 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js @@ -650,6 +650,7 @@ define('pgadmin.node.exclusion_constraint', [ condeferrable: undefined, condeferred: undefined, columns: [], + include: [], }, // Define the schema for the exclusion constraint node @@ -891,6 +892,103 @@ define('pgadmin.node.exclusion_constraint', [ Backgrid.StringCell.prototype.remove.apply(this, arguments); }, }), + },{ + id: 'include', label: gettext('Include columns'), + type: 'array', group: gettext('Columns'), + editable: false, + canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'], + visible: function(m) { + /* In table properties, m.node_info is not available */ + m = m.top; + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 110000) + return true; + + return false; + }, + control: Backform.MultiSelectAjaxControl.extend({ + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + allowClear: false, + width: 'style', + multiple: true, + placeholder: gettext('Select the column(s)'), + }, + } + ), + initialize: function() { + // Here we will decide if we need to call URL + // Or fetch the data from parent columns collection + var self = this; + if(this.model.handler) { + Backform.Select2Control.prototype.initialize.apply(this, arguments); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.resetColOptions); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + self.custom_options(); + } else { + Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments); + } + }, + resetColOptions: function() { + var self = this; + + setTimeout(function () { + self.custom_options(); + self.render.apply(self); + }, 50); + }, + custom_options: function() { + // We will add all the columns entered by user in table model + var columns = this.model.top.get('columns'), + added_columns_from_tables = []; + + if (columns.length > 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push( + {label: col, value: col, image:'icon-column'} + ); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + }, + }), + deps: ['index'], node: 'column', + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should be allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update columns of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + var col = m.get('columns'); + col.reset(); + return true; + } + }, }], validate: function() { this.errorModel.clear(); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/__init__.py new file mode 100644 index 00000000..590026ad --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/__init__.py @@ -0,0 +1,8 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_add.py new file mode 100644 index 00000000..81ce5333 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_add.py @@ -0,0 +1,80 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_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 exclusion_utils + + +class ExclusionConstraintAddTestCase(BaseTestGenerator): + """This class will add new exclusion constraint to existing table""" + scenarios = [ + ('Add Exclusion Constraint URL', + dict(url='/browser/exclusion_constraint/obj/')) + ] + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_exclusion_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def runTest(self): + """This function will add exclusion constraint to existing table.""" + self.index_name = "test_index_add_%s" % (str(uuid.uuid4())[1:8]) + data = {"name": self.index_name, + "spcname": "pg_default", + "amname": "btree", + "columns": [ + {"column": "id", "sort_order": False, "nulls": False, + "operator": "="}], + "include": ["name"] + } + 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), + content_type='html/json') + self.assertEquals(response.status_code, 200) + + index_response = exclusion_utils.verify_exclusion_constraint( + self.server, self.db_name, self.index_name) + + if not index_response: + raise Exception("Could not find the constraint added.") + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_delete.py new file mode 100644 index 00000000..1be69c8f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_delete.py @@ -0,0 +1,79 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_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 exclusion_utils + + +class ExclusionConstraintDeleteTestCase(BaseTestGenerator): + """This class will delete the existing exclusion constraint of table.""" + scenarios = [ + ('Delete Exclusion Constraint Node URL', + dict(url='/browser/exclusion_constraint/obj/')) + ] + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_exclusion_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.index_name = "test_exclusion_delete_%s" % (str(uuid.uuid4())[1:8]) + self.index_id = exclusion_utils.create_exclusion_constraint( + self.server, self.db_name, self.schema_name, self.table_name, + self.index_name + ) + + def runTest(self): + """This function will delete exclusion constraint.""" + index_response = exclusion_utils.verify_exclusion_constraint( + self.server, self.db_name, self.index_name) + if not index_response: + raise Exception("Could not find the constraint to delete.") + response = self.tester.delete(self.url + str(utils.SERVER_GROUP) + + '/' + str(self.server_id) + '/' + + str(self.db_id) + '/' + + str(self.schema_id) + '/' + + str(self.table_id) + '/' + + str(self.index_id), + follow_redirects=True) + self.assertEquals(response.status_code, 200) + + index_response = exclusion_utils.verify_exclusion_constraint( + self.server, self.db_name, self.index_name) + if index_response: + raise Exception("Constraint is not deleted.") + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_get.py new file mode 100644 index 00000000..0627f389 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_get.py @@ -0,0 +1,69 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_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 exclusion_utils + + +class ExclusionGetTestCase(BaseTestGenerator): + """This class will fetch the existing exclusion constraint""" + scenarios = [ + ('Fetch Exclusion Constraint', + dict(url='/browser/exclusion_constraint/obj/')) + ] + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_exclusion_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.index_name = "test_exclusion_get_%s" % (str(uuid.uuid4())[1:8]) + self.index_id = exclusion_utils.create_exclusion_constraint( + self.server, self.db_name, self.schema_name, self.table_name, + self.index_name + ) + + def runTest(self): + """This function will fetch the existing exclusion constraint.""" + response = self.tester.get( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.index_id), + follow_redirects=True) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_put.py new file mode 100644 index 00000000..49186d51 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/test_exclusion_constraint_put.py @@ -0,0 +1,77 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_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 exclusion_utils + + +class IndexesUpdateTestCase(BaseTestGenerator): + """This class will update the existing exclusion constraint.""" + scenarios = [ + ('Put exclusion constraint URL', + dict(url='/browser/exclusion_constraint/obj/')) + ] + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.index_name = "test_exclusion_delete_%s" % (str(uuid.uuid4())[1:8]) + self.index_id = exclusion_utils.create_exclusion_constraint( + self.server, self.db_name, self.schema_name, self.table_name, + self.index_name + ) + + def runTest(self): + """This function will update an existing exclusion constraint""" + index_response = exclusion_utils.verify_exclusion_constraint( + self.server, self.db_name, self.index_name) + if not index_response: + raise Exception("Could not find the exclusion constraint.") + data = {"oid": self.index_id, + "comment": "This is test comment for index"} + response = self.tester.put( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.index_id), + data=json.dumps(data), + follow_redirects=True) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/utils.py new file mode 100644 index 00000000..d2099abb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/tests/utils.py @@ -0,0 +1,89 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from __future__ import print_function + +import sys +import traceback + +from regression.python_test_utils import test_utils as utils + + +def create_exclusion_constraint(server, db_name, schema_name, table_name, + key_name): + """ + This function creates a exclusion constraint 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 key_name: test name for key + :type key_name: str + :return oid: key constraint 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 CONSTRAINT %s EXCLUDE USING btree(" \ + "id ASC NULLS FIRST WITH =)" % \ + (schema_name, table_name, key_name) + pg_cursor.execute(query) + connection.set_isolation_level(old_isolation_level) + connection.commit() + # Get oid of newly added index constraint + pg_cursor.execute( + "SELECT conindid FROM pg_constraint where conname='%s'" % key_name) + index_constraint = pg_cursor.fetchone() + connection.close() + oid = index_constraint[0] + return oid + except Exception: + traceback.print_exc(file=sys.stderr) + + +def verify_exclusion_constraint(server, db_name, index_name): + """ + This function verifies index exist or not. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param index_name: index name + :type index_name: str + :return table: table record from database + :rtype: tuple + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("select * from pg_class where relname='%s'" % + index_name) + index_record = pg_cursor.fetchone() + connection.close() + return index_record + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py index 36f4eb05..e60e63bb 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py @@ -1118,7 +1118,7 @@ class ForeignKeyConstraintView(PGChildNodeView): sql = render_template( "/".join([self.template_path, 'get_cols.sql']), cid=costrnt['oid'], - colcnt=costrnt['indnatts']) + colcnt=costrnt['col_count']) status, rest = self.conn.execute_dict(sql) if not status: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py index cdeef40c..5e4f9d90 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py @@ -263,7 +263,8 @@ class IndexConstraintView(PGChildNodeView): kwargs['sid'] ) self.conn = self.manager.connection(did=kwargs['did']) - self.template_path = 'index_constraint/sql' + self.template_path = 'index_constraint/sql/#{0}#'\ + .format(self.manager.version) # We need parent's name eg table name and schema name SQL = render_template("/".join([self.template_path, @@ -323,7 +324,7 @@ class IndexConstraintView(PGChildNodeView): sql = render_template( "/".join([self.template_path, 'get_constraint_cols.sql']), cid=cid, - colcnt=result['indnatts']) + colcnt=result['col_count']) status, res = self.conn.execute_dict(sql) if not status: @@ -335,6 +336,18 @@ class IndexConstraintView(PGChildNodeView): result['columns'] = columns + # Add Include details of the index supported for PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join([self.template_path, 'get_constraint_include.sql']), + cid=cid) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result['include'] = [col['colname'] for col in res['rows']] + return ajax_response( response=result, status=200 @@ -384,7 +397,8 @@ class IndexConstraintView(PGChildNodeView): """ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) self.conn = self.manager.connection(did=did) - self.template_path = 'index_constraint/sql' + self.template_path = 'index_constraint/sql/#{0}#'\ + .format(self.manager.version) # We need parent's name eg table name and schema name SQL = render_template("/".join([self.template_path, @@ -505,7 +519,8 @@ class IndexConstraintView(PGChildNodeView): """ self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) self.conn = self.manager.connection(did=did) - self.template_path = 'index_constraint/sql' + self.template_path = 'index_constraint/sql/#{0}#'\ + .format(self.manager.version) # We need parent's name eg table name and schema name SQL = render_template("/".join([self.template_path, @@ -935,7 +950,7 @@ class IndexConstraintView(PGChildNodeView): sql = render_template( "/".join([self.template_path, 'get_constraint_cols.sql']), - cid=cid, colcnt=data['indnatts']) + cid=cid, colcnt=data['col_count']) status, res = self.conn.execute_dict(sql) @@ -948,6 +963,18 @@ class IndexConstraintView(PGChildNodeView): data['columns'] = columns + # Add Include details of the index supported for PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join([self.template_path, 'get_constraint_include.sql']), + cid=cid) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + data['include'] = [col['colname'] for col in res['rows']] + SQL = render_template( "/".join([self.template_path, 'create.sql']), data=data, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js index 0ad0f054..e599b283 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js @@ -100,6 +100,7 @@ define('pgadmin.node.primary_key', [ condeferrable: undefined, condeferred: undefined, columns: [], + include: [], }, // Define the schema for the index constraint node @@ -400,6 +401,103 @@ define('pgadmin.node.primary_key', [ } }, },{ + id: 'include', label: gettext('Include columns'), + type: 'array', group: gettext('Definition'), + editable: false, + canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'], + visible: function(m) { + /* In table properties, m.node_info is not available */ + m = m.top; + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 110000) + return true; + + return false; + }, + control: Backform.MultiSelectAjaxControl.extend({ + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + allowClear: false, + width: 'style', + multiple: true, + placeholder: gettext('Select the column(s)'), + }, + } + ), + initialize: function() { + // Here we will decide if we need to call URL + // Or fetch the data from parent columns collection + var self = this; + if(this.model.handler) { + Backform.Select2Control.prototype.initialize.apply(this, arguments); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.resetColOptions); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + self.custom_options(); + } else { + Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments); + } + }, + resetColOptions: function() { + var self = this; + + setTimeout(function () { + self.custom_options(); + self.render.apply(self); + }, 50); + }, + custom_options: function() { + // We will add all the columns entered by user in table model + var columns = this.model.top.get('columns'), + added_columns_from_tables = []; + + if (columns.length > 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push( + {label: col, value: col, image:'icon-column'} + ); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + }, + }), + deps: ['index'], node: 'column', + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should be allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update columns of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + var col = m.get('columns'); + col.reset(); + return true; + } + }, + },{ id: 'spcname', label: gettext('Tablespace'), type: 'text', group: gettext('Definition'), control: 'node-list-by-name', node: 'tablespace', diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js index 18d3ca33..f8b8c2cc 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js @@ -86,6 +86,7 @@ define('pgadmin.node.unique_constraint', [ condeferrable: undefined, condeferred: undefined, columns: [], + include: [], }, // Define the schema for the index constraint node @@ -386,6 +387,103 @@ define('pgadmin.node.unique_constraint', [ } }, },{ + id: 'include', label: gettext('Include columns'), + type: 'array', group: gettext('Definition'), + editable: false, + canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'], + visible: function(m) { + /* In table properties, m.node_info is not available */ + m = m.top; + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 110000) + return true; + + return false; + }, + control: Backform.MultiSelectAjaxControl.extend({ + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + allowClear: false, + width: 'style', + multiple: true, + placeholder: gettext('Select the column(s)'), + }, + } + ), + initialize: function() { + // Here we will decide if we need to call URL + // Or fetch the data from parent columns collection + var self = this; + if(this.model.handler) { + Backform.Select2Control.prototype.initialize.apply(this, arguments); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.resetColOptions); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + self.custom_options(); + } else { + Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments); + } + }, + resetColOptions: function() { + var self = this; + + setTimeout(function () { + self.custom_options(); + self.render.apply(self); + }, 50); + }, + custom_options: function() { + // We will add all the columns entered by user in table model + var columns = this.model.top.get('columns'), + added_columns_from_tables = []; + + if (columns.length > 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push( + {label: col, value: col, image:'icon-column'} + ); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + }, + }), + deps: ['index'], node: 'column', + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should be allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update columns of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + var col = m.get('columns'); + col.reset(); + return true; + } + }, + },{ id: 'spcname', label: gettext('Tablespace'), type: 'text', group: gettext('Definition'), control: 'node-list-by-name', node: 'tablespace', diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py index 44a020c3..1acdb8ef 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py @@ -527,10 +527,35 @@ class IndexesView(PGChildNodeView): # Push as collection data['columns'] = columns # Push as string - data['cols'] = ', '.join(cols) + data['columns_csv'] = ', '.join(cols) return data + def _include_details(self, idx, data, mode='properties'): + """ + This functional will fetch list of include details for index + supported with Postgres 11+ + + Args: + idx: Index OID + data: Properties data + + Returns: + Updated properties data with include details + """ + + SQL = render_template( + "/".join([self.template_path, 'include_details.sql']), idx=idx + ) + status, rset = self.conn.execute_2darray(SQL) + + if not status: + return internal_server_error(errormsg=rset) + + # Push as collection + data['include'] = [col['colname'] for col in rset['rows']] + return data + @check_precondition def properties(self, gid, sid, did, scid, tid, idx): """ @@ -568,6 +593,10 @@ class IndexesView(PGChildNodeView): # Add column details for current index data = self._column_details(idx, data) + # Add Include details of the index + if self.manager.version >= 110000: + data = self._include_details(idx, data) + return ajax_response( response=data, status=200 @@ -905,6 +934,10 @@ class IndexesView(PGChildNodeView): # Add column details for current index data = self._column_details(idx, data, 'create') + # Add Include details of the index + if self.manager.version >= 110000: + data = self._include_details(idx, data, 'create') + SQL, name = self.get_sql(did, scid, tid, None, data) if not isinstance(SQL, (str, unicode)): return SQL diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js index e58bb463..1da58ae9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js @@ -226,6 +226,7 @@ define('pgadmin.node.index', [ hasDepends: true, hasStatistics: true, statsPrettifyFields: ['Size', 'Index size'], + width: '45%', Init: function() { /* Avoid mulitple registration of menus */ if (this.initialized) @@ -331,9 +332,47 @@ define('pgadmin.node.index', [ }, }), },{ - id: 'cols', label: gettext('Columns'), cell: 'string', + id: 'columns_csv', label: gettext('Columns'), cell: 'string', type: 'text', disabled: 'inSchema', mode: ['properties'], group: gettext('Definition'), + },{ + id: 'include', label: gettext('Include columns'), + type: 'array', group: gettext('Definition'), + editable: false, + canDelete: true, canAdd: true, mode: ['properties'], + disabled: 'inSchemaWithModelCheck', + visible: function(m) { + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 110000) + return true; + + return false; + }, + control: Backform.MultiSelectAjaxControl.extend({ + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + allowClear: false, + width: 'style', + multiple: true, + placeholder: gettext('Select the column(s)'), + }, + } + ), + }), + transform : function(data){ + var res = []; + if (data && _.isArray(data)) { + _.each(data, function(d) { + res.push({label: d.label, value: d.label, image:'icon-column'}); + }); + } + return res; + }, + node:'column', },{ id: 'fillfactor', label: gettext('Fill factor'), cell: 'string', type: 'int', disabled: 'inSchema', mode: ['create', 'edit', 'properties'], @@ -387,6 +426,44 @@ define('pgadmin.node.index', [ }, control: 'unique-col-collection', uniqueCol : ['colname'], columns: ['colname', 'op_class', 'sort_order', 'nulls', 'collspcname'], + },{ + id: 'include', label: gettext('Include columns'), + type: 'array', group: gettext('Definition'), + editable: false, + canDelete: true, canAdd: true, mode: ['edit', 'create'], + disabled: 'inSchemaWithModelCheck', + visible: function(m) { + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 110000) + return true; + + return false; + }, + control: Backform.MultiSelectAjaxControl.extend({ + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + allowClear: false, + width: 'style', + multiple: true, + placeholder: gettext('Select the column(s)'), + }, + } + ), + }), + transform : function(data){ + var res = []; + if (data && _.isArray(data)) { + _.each(data, function(d) { + res.push({label: d.label, value: d.label, image:'icon-column'}); + }); + } + return res; + }, + node:'column', },{ id: 'description', label: gettext('Comment'), cell: 'string', type: 'multiline', mode: ['properties', 'create', 'edit'], diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/tests/test_indexes_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/tests/test_indexes_add.py index 11fc64be..7b9606dd 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/tests/test_indexes_add.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/tests/test_indexes_add.py @@ -55,7 +55,9 @@ class IndexesAddTestCase(BaseTestGenerator): "spcname": "pg_default", "amname": "btree", "columns": [ - {"colname": "id", "sort_order": False, "nulls": False}]} + {"colname": "id", "sort_order": False, "nulls": False}], + "include": ["name"] + } response = self.tester.post( self.url + str(utils.SERVER_GROUP) + '/' + str(self.server_id) + '/' + str(self.db_id) + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/create.sql new file mode 100644 index 00000000..26fd2dda --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/create.sql @@ -0,0 +1,21 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( + {% for col in data.columns %}{% if loop.index != 1 %}, + {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% 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 %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%}{% if data.constraint %} WHERE ({{data.constraint}}){% 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 }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/get_constraint_include.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/get_constraint_include.sql new file mode 100644 index 00000000..73e7c59b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/get_constraint_include.sql @@ -0,0 +1,16 @@ +-- pg_get_indexdef did not support INCLUDE columns + +SELECT a.attname as colname +FROM ( + SELECT + i.indnkeyatts, + i.indrelid, + unnest(indkey) AS table_colnum, + unnest(ARRAY(SELECT generate_series(1, i.indnatts) AS n)) attnum + FROM + pg_index i + WHERE i.indexrelid = {{cid}}::OID +) i JOIN pg_attribute a +ON (a.attrelid = i.indrelid AND i.table_colnum = a.attnum) +WHERE i.attnum > i.indnkeyatts +ORDER BY i.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/properties.sql new file mode 100644 index 00000000..44abceed --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/11_plus/properties.sql @@ -0,0 +1,34 @@ +SELECT cls.oid, + cls.relname as name, + indnkeyatts as col_count, + amname, + CASE WHEN length(spcname) > 0 THEN spcname ELSE + (SELECT sp.spcname FROM pg_database dtb + JOIN 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, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +JOIN pg_class tab ON tab.oid=indrelid +LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace +JOIN pg_namespace n ON n.oid=tab.relnamespace +JOIN pg_am am ON am.oid=cls.relam +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN 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='x' +ORDER BY cls.relname diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/default/properties.sql index ba3a981e..3372b695 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/default/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/default/properties.sql @@ -1,6 +1,6 @@ SELECT cls.oid, cls.relname as name, - indnatts, + indnatts as col_count, amname, CASE WHEN length(spcname) > 0 THEN spcname ELSE (SELECT sp.spcname FROM pg_database dtb @@ -31,4 +31,4 @@ WHERE indrelid = {{tid}}::oid AND cls.oid = {{cid}}::oid {% endif %} AND contype='x' -ORDER BY cls.relname \ No newline at end of file +ORDER BY cls.relname diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/default/get_constraints.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/default/get_constraints.sql index d2f358cd..9313a030 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/default/get_constraints.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/default/get_constraints.sql @@ -1,4 +1,4 @@ -SELECT cls.oid, cls.relname as idxname, indnatts +SELECT cls.oid, cls.relname as idxname, indnatts as col_count FROM pg_index idx JOIN pg_class cls ON cls.oid=indexrelid LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') @@ -34,4 +34,4 @@ SELECT cls.oid, cls.relname as idxname, indnatts LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) WHERE idx.indrelid = {{tid}}::oid - AND conname IS NULL \ No newline at end of file + AND conname IS NULL diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/column_details.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/column_details.sql new file mode 100644 index 00000000..46bc4b0c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/column_details.sql @@ -0,0 +1,31 @@ +SELECT + i.indexrelid, + CASE i.indoption[i.attnum - 1] + WHEN 0 THEN ARRAY['ASC', 'NULLS LAST'] + WHEN 1 THEN ARRAY['DESC', 'NULLS FIRST'] + WHEN 2 THEN ARRAY['ASC', 'NULLS FIRST'] + WHEN 3 THEN ARRAY['DESC', 'NULLS '] + ELSE ARRAY['UNKNOWN OPTION' || i.indoption[i.attnum - 1], ''] + END::text[] AS options, + i.attnum, + pg_get_indexdef(i.indexrelid, i.attnum, true) as attdef, + CASE WHEN (o.opcdefault = FALSE) THEN o.opcname ELSE null END AS opcname, + op.oprname AS oprname, + CASE WHEN length(nspc.nspname) > 0 AND length(coll.collname) > 0 THEN + concat(quote_ident(nspc.nspname), '.', quote_ident(coll.collname)) + ELSE '' END AS collnspname +FROM ( + SELECT + indexrelid, i.indoption, i.indclass, + unnest(ARRAY(SELECT generate_series(1, i.indnkeyatts) AS n)) AS attnum + FROM + pg_index i + WHERE i.indexrelid = {{idx}}::OID +) i + LEFT JOIN pg_opclass o ON (o.oid = i.indclass[i.attnum - 1]) + LEFT OUTER JOIN pg_constraint c ON (c.conindid = i.indexrelid) + LEFT OUTER JOIN pg_operator op ON (op.oid = c.conexclop[i.attnum]) + LEFT JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND a.attnum = i.attnum) + LEFT OUTER JOIN pg_collation coll ON a.attcollation=coll.oid + LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid +ORDER BY i.attnum; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/create.sql new file mode 100644 index 00000000..2fd90d79 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/create.sql @@ -0,0 +1,25 @@ +CREATE {% if data.indisunique %}UNIQUE {% endif %}INDEX {% if data.isconcurrent %}CONCURRENTLY {% endif %}{{conn|qtIdent(data.name)}} + ON {{conn|qtIdent(data.schema, data.table)}} {% if data.amname %}USING {{conn|qtIdent(data.amname)}}{% endif %} + +{% if mode == 'create' %} + ({% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.colname)}}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.op_class %} + {{c.op_class}}{% endif %}{% if data.amname is defined %}{% if c.sort_order is defined and c.is_sort_nulls_applicable %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined and c.is_sort_nulls_applicable %} NULLS {% if c.nulls %} +FIRST{% else %}LAST{% endif %}{% endif %}{% endif %}{% endfor %}) +{% if data.include|length > 0 %} + INCLUDE({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}) +{% endif %} +{% else %} +{## We will get indented data from postgres for column ##} + ({% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{c.colname}}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.op_class %} + {{c.op_class}}{% endif %}{% if c.sort_order is defined %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined %} NULLS {% if c.nulls %} +FIRST{% else %}LAST{% endif %}{% endif %}{% endfor %}) +{% if data.include|length > 0 %} + INCLUDE({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}) +{% endif %} +{% endif %} +{% if data.fillfactor %} + WITH (FILLFACTOR={{data.fillfactor}}) +{% endif %}{% if data.spcname %} + TABLESPACE {{conn|qtIdent(data.spcname)}}{% endif %}{% if data.indconstraint %} + WHERE {{data.indconstraint}} +{% endif %}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/include_details.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/include_details.sql new file mode 100644 index 00000000..49d6173c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/11_plus/include_details.sql @@ -0,0 +1,16 @@ +-- pg_get_indexdef did not support INCLUDE columns + +SELECT a.attname as colname +FROM ( + SELECT + i.indnkeyatts, + i.indrelid, + unnest(indkey) AS table_colnum, + unnest(ARRAY(SELECT generate_series(1, i.indnatts) AS n)) attnum + FROM + pg_index i + WHERE i.indexrelid = {{idx}}::OID +) i JOIN pg_attribute a +ON (a.attrelid = i.indrelid AND i.table_colnum = a.attnum) +WHERE i.attnum > i.indnkeyatts +ORDER BY i.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/create.sql new file mode 100644 index 00000000..5d85ec7b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/create.sql @@ -0,0 +1,20 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} {{constraint_name}} {% 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 }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/get_constraint_include.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/get_constraint_include.sql new file mode 100644 index 00000000..73e7c59b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/get_constraint_include.sql @@ -0,0 +1,16 @@ +-- pg_get_indexdef did not support INCLUDE columns + +SELECT a.attname as colname +FROM ( + SELECT + i.indnkeyatts, + i.indrelid, + unnest(indkey) AS table_colnum, + unnest(ARRAY(SELECT generate_series(1, i.indnatts) AS n)) attnum + FROM + pg_index i + WHERE i.indexrelid = {{cid}}::OID +) i JOIN pg_attribute a +ON (a.attrelid = i.indrelid AND i.table_colnum = a.attnum) +WHERE i.attnum > i.indnkeyatts +ORDER BY i.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/properties.sql new file mode 100644 index 00000000..3dda5633 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/11_plus/properties.sql @@ -0,0 +1,33 @@ +SELECT cls.oid, + cls.relname as name, + indnkeyatts as col_count, + CASE WHEN length(spcname) > 0 THEN spcname ELSE + (SELECT sp.spcname FROM pg_database dtb + JOIN 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, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +JOIN pg_class tab ON tab.oid=indrelid +LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace +JOIN pg_namespace n ON n.oid=tab.relnamespace +JOIN pg_am am ON am.oid=cls.relam +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN 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 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/begin.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/begin.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/create.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/create.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/delete.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/delete.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/end.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/end.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_constraint_cols.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_constraint_cols.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_indices.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_indices.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_name.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_name.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_oid.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_oid.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_oid_with_transaction.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_oid_with_transaction.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_parent.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/get_parent.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/nodes.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/nodes.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/properties.sql similarity index 96% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/properties.sql index 9e392ebe..6ead04b4 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/properties.sql @@ -1,6 +1,6 @@ SELECT cls.oid, cls.relname as name, - indnatts, + indnatts as col_count, CASE WHEN length(spcname) > 0 THEN spcname ELSE (SELECT sp.spcname FROM pg_database dtb JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid @@ -30,4 +30,4 @@ WHERE indrelid = {{tid}}::oid AND cls.oid = {{cid}}::oid {% endif %} AND contype='{{constraint_type}}' -ORDER BY cls.relname \ No newline at end of file +ORDER BY cls.relname diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/stats.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/stats.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/stats.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/update.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/default/update.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro index 8165f320..6eb04ca8 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro @@ -7,7 +7,10 @@ {% if data.columns|length > 0 %} {% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}PRIMARY KEY ({% for c in data.columns%} -{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.column)}}{% endfor %}){% if data.fillfactor %} +{% 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 %} +{% if data.fillfactor %} WITH (FILLFACTOR={{data.fillfactor}}){% endif %} {% if data.spcname and data.spcname != "pg_default" %} @@ -23,7 +26,10 @@ {% 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 loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.column)}}{% endfor %}){% if data.fillfactor %} +{% 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 %} +{% if data.fillfactor %} WITH (FILLFACTOR={{data.fillfactor}}){% endif %} {% if data.spcname and data.spcname != "pg_default" %} @@ -78,9 +84,11 @@ {% if data.name %}CONSTRAINT {{ conn|qtIdent(data.name) }} {% endif%}EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( {% for col in data.columns %}{% if loop.index != 1 %}, - {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %} - WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %} - + {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% 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 %} {% if data.condeferrable %} @@ -99,4 +107,4 @@ COMMENT ON CONSTRAINT {{ conn|qtIdent(d.name) }} ON {{ conn|qtIdent(schema, tabl IS {{ d.comment|qtLiteral }}; {% endif %} {% endfor %} -{%- endmacro %} \ No newline at end of file +{%- endmacro %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py index 2faabd07..7264dd72 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py @@ -127,7 +127,8 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): 'exclusion_constraint/sql', server_type, ver) # Template for PK & Unique constraint node - self.index_constraint_template_path = 'index_constraint/sql' + self.index_constraint_template_path = 'index_constraint/sql/#{0}#'\ + .format(ver) # Template for foreign key constraint node self.foreign_key_template_path = compile_template_path( @@ -368,7 +369,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): "/".join([self.index_constraint_template_path, 'get_constraint_cols.sql']), cid=row['oid'], - colcnt=row['indnatts']) + colcnt=row['col_count']) status, res = self.conn.execute_dict(sql) if not status: @@ -380,6 +381,19 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): result['columns'] = columns + # INCLUDE clause in index is supported from PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join([self.index_constraint_template_path, + 'get_constraint_include.sql']), + cid=row['oid']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result['include'] = [col['colname'] for col in res['rows']] + # If not exists then create list and/or append into # existing list [ Adding into main data dict] data.setdefault(index_constraints[ctype], []).append(result) @@ -513,7 +527,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): [self.exclusion_constraint_template_path, 'get_constraint_cols.sql']), cid=ex['oid'], - colcnt=ex['indnatts']) + colcnt=ex['col_count']) status, res = self.conn.execute_dict(sql) @@ -538,6 +552,20 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): }) ex['columns'] = columns + + # INCLUDE clause in index is supported from PG-11+ + if self.manager.version >= 110000: + sql = render_template( + "/".join([self.exclusion_constraint_template_path, + 'get_constraint_include.sql']), + cid=ex['oid']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + ex['include'] = [col['colname'] for col in res['rows']] + # If not exists then create list and/or append into # existing list [ Adding into main data dict] data.setdefault('exclude_constraint', []).append(ex) @@ -962,6 +990,18 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): # Push as string data['cols'] = ', '.join(cols) + if self.manager.version >= 110000: + SQL = render_template( + "/".join([self.index_template_path, + 'include_details.sql']), + idx=row['oid']) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + data['include'] = [col['colname'] for col in res['rows']] + sql_header = u"\n-- Index: {0}\n\n-- ".format(data['name']) sql_header += render_template("/".join([self.index_template_path, @@ -2389,7 +2429,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): sql = render_template( "/".join([self.foreign_key_template_path, 'get_cols.sql']), cid=costrnt['oid'], - colcnt=costrnt['indnatts']) + colcnt=costrnt['col_count']) status, rest = self.conn.execute_dict(sql) if not status: diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index d8f2ce67..63a50450 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -12,6 +12,7 @@ import codecs import os import pickle import random +import sys import simplejson as json from flask import Response, url_for, render_template, session, request, \ @@ -50,6 +51,11 @@ try: except ImportError: from urllib.parse import unquote +if sys.version_info[0:2] <= (2, 7): + IS_PY2 = True +else: + IS_PY2 = False + class SqlEditorModule(PgAdminModule): """ @@ -310,8 +316,10 @@ def extract_sql_from_network_parameters(request_data, request_arguments, request_form_data): if request_data: sql_parameters = json.loads(request_data, encoding='utf-8') - if type(sql_parameters) is str: - return dict(sql=sql_parameters, explain_plan=None) + + if (IS_PY2 and type(sql_parameters) is unicode) \ + or type(sql_parameters) is str: + return dict(sql=str(sql_parameters), explain_plan=None) return sql_parameters else: return request_arguments or request_form_data