public inbox for [email protected]help / color / mirror / Atom feed
[pgAdmin4] PATCH: View and Role Node 7+ messages / 2 participants [nested] [flat]
* [pgAdmin4] PATCH: View and Role Node @ 2016-02-02 08:29 Surinder Kumar <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Surinder Kumar @ 2016-02-02 08:29 UTC (permalink / raw) To: pgadmin-hackers Hi, Please find the patch for the View and its child node Rule. You have to apply database node and SqlFieldControl patch to run it. Please review it and Let me know for any comments. Regards, Surinder Kumar -- Sent via pgadmin-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers Attachments: [application/octet-stream] View_and_Roles_node.patch (158.2K, 3-View_and_Roles_node.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py new file mode 100644 index 0000000..9776122 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -0,0 +1,609 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as databases +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from .parse_definition import parse_definition + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str,bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class ViewModule(CollectionNodeModule): + NODE_TYPE = 'view' + COLLECTION_LABEL = gettext("Views") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(ViewModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that scid is catalog and schema object + # and catalog name is 'sys', 'dbo', 'information_schema' then only we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs then allow us to + make dission if we want to load this module or not for that schema + """ + if super(ViewModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + # we will set template path for sql scripts + if server_type == 'ppas' and ver >= 90100: + template_path = 'templates/views/sql/edb_9.1_plus' + elif ver >= 90100: + template_path = 'templates/views/sql/9.4_plus' + else: + # Note: View is not supported below postgres version 9.1 + # Hence we will not load this module + return False + + SQL = render_template("/".join([template_path, 'backend_support.sql']), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', 'information_schema', + # then False (Do not load this module), othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for view, when any of the database node is + initialized. it is because views are under catalogs and schemas . + """ + return databases.DatabaseModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "templates/views/css/view.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + +# Set template_folder to views,as view also uses rules sql templates. +blueprint = ViewModule(__name__, template_folder="../views") + + +class ViewNode(PGChildNodeView): + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'vid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "templates/views/js/views.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + ver = self.manager.version + # we will set template path for sql scripts + if ver >= 90400: + self.template_path = 'templates/views/sql/9.4_plus' + elif ver >= 90300: + self.template_path = 'templates/views/sql/9.3_plus' + elif ver >= 90200: + self.template_path = 'templates/views/sql/9.2_plus' + elif ver >= 90100: + self.template_path = 'templates/views/sql/9.1_plus' + else: + self.template_path = 'templates/views/sql/pre_9.1' + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid): + """ + It fetches all views properties and render into properties + tab + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), scid=scid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + It lists down the all views under the Views Collection node + """ + res = [] + SQL = render_template("/".join([self.template_path, 'properties.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + row['name'], + icon="pg-icon-view" + )) + + return make_json_response( + data=res, + status=200 + ) + + def parse_views_privileges(self, db_privileges): + """ + This function forms separate list of grantable + and non grantable privileges. + """ + acl = {'grantor':db_privileges['grantor'], + 'grantee':db_privileges['grantee'], + 'privileges':[] + } + + privileges = [] + for idx, priv in enumerate(db_privileges['privileges']): + if db_privileges['grantable'][idx]: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": True + }) + else: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": False + }) + + acl['privileges'] = privileges + + return acl + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + It fetches the properties of an individual view + and render in properties tab + + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), vid=vid, datlastsysoid = self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join([self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + # sending result to formtter + frmtd_reslt = self.formatter(result) + # merging formated result with main result again + result.update(frmtd_reslt) + return ajax_response( + response=result, + status=200 + ) + + @staticmethod + def formatter(result): + """ This function formats output for + security label & variables""" + frmtd_result = dict() + sec_lbls = [] + if 'seclabels' in result and result['seclabels'] is not None: + for sec in result['seclabels']: + sec = re.search(r'([^=]+)=(.*$)', sec) + sec_lbls.append({ + 'provider': sec.group(1), + 'security_label': sec.group(2) + }) + + frmtd_result.update({"seclabels" :sec_lbls}) + # returning final result + return frmtd_result + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will creates new view object + """ + required_args = [ + 'name', + 'definition' + ] + + data = request.form if request.form else json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join([self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join([self.template_path, 'view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=view_id) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + data['name'], + icon="pg-icon-view" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid): + """ + This function will update view object + """ + data = request.form if request.form else json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid) + try: + if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="View updated", + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid): + """ + This function will drop the view object + """ + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + # Get name for view from did + SQL = render_template("/".join([self.template_path, 'delete.sql']), vid=vid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=name) + # drop view + SQL = render_template("/".join([self.template_path, 'delete.sql']), nspname=res_data['rows'][0]['nspname'], name=res_data['rows'][0]['name'], cascade=cascade) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid=None): + """ + This function to return modified SQL + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + SQL = self.getSQL(gid, sid, data, vid) + if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data="--modified SQL", + status=200 + ) + + @staticmethod + def parse_privileges(str_privileges, object_type= 'VIEW'): + """Parse Privileges.""" + db_privileges = { + 'a': 'INSERT', + 'r': 'SELECT', + 'w': 'UPDATE', + 'd': 'DELETE', + 'x': 'REFERENCE', + 't': 'TRIGGER' + } + privileges = [] + for priv in str_privileges: + priv_with_grant = [] + priv_without_grant = [] + for privilege in priv['privileges']: + if privilege['with_grant']: + priv_with_grant.append(db_privileges[privilege['privilege_type']]) + elif privilege['privilege']: + priv_without_grant.append(db_privileges[privilege['privilege_type']]) + priv_with_grant = ", ".join(priv_with_grant) + priv_without_grant = ", ".join(priv_without_grant) + # Server Level validation + if 'grantee' in priv: + privileges.append({'grantee': priv['grantee'] if 'grantee' in priv else '', 'with_grant': priv_with_grant, 'without_grant': priv_without_grant}) + else: + return '' + return privileges + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will genrate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join([self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid = self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges(data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges(data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges(data[key]['deleted']) + SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, + o_data=old_data, conn=self.conn) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join([self.template_path, 'create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join([self.template_path, 'grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def sql(self, gid, sid, did, scid, vid): + """ + This function will generate sql to render into sql panel + """ + SQL_data = '' + SQL = render_template("/".join([self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid = self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch all privileges for view + SQL = render_template("/".join([self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0]['datacl'].append(priv) + else: + res['rows'][0]['datacl'] = [priv] + + result = res['rows'][0] + if 'datacl' in result: + result['datacl'] = self.parse_privileges(result['datacl']) + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=result, + conn=self.conn, + display_comments=True + ) + SQL += "\n" + SQL += render_template("/".join([self.template_path, 'grant.sql']), data=result) + + SQL_data += SQL + # Get all non system rules of view node, generate their sql and render + # into sql tab + self.temp_path = 'rules/templates/rules/sql' + SQL = render_template("/".join([self.temp_path, 'properties.sql']), vid=vid) + status, data_prop = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for rule in data_prop['rows']: + # Generate SQL only for non system rule + if rule['name'] != '_RETURN': + res = [] + SQL = render_template("/".join([self.temp_path, 'properties.sql']), + rid=rule['oid'] + ) + status, res = self.conn.execute_dict(SQL) + res_data = parse_definition(res) + SQL = render_template("/".join([self.temp_path, 'create.sql']), + data=res_data, display_comments=True) + SQL_data += '\n' + SQL_data +=SQL + + return ajax_response(response=SQL_data) + +ViewNode.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py new file mode 100644 index 0000000..257be61 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py @@ -0,0 +1,54 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +def parse_definition(res): + """ + This function extracts: + - events + - do_instead + - statements + - condition + from the defintion row, form an array with + fields and return it. + """ + res_data = [] + try: + res_data = res['rows'][0] + data_def = res_data['definition'] + import re + # Parse data for event + e_match = re.search(r"ON\s+(.*)\s+TO", data_def) + event_data = e_match.group(1) if e_match is not None else None + event = event_data if event_data is not None else '' + + # Parse data for do instead + inst_match = re.search(r"\s+(INSTEAD)\s+", data_def) + instead_data = inst_match.group(1) if inst_match is not None else None + instead = True if instead_data is not None else False + + # Parse data for condition + condition_match = re.search(r"(?:WHERE)\s+(.*)\s+(?:DO)", data_def) + condition_data = condition_match.group(1) if condition_match is not None\ + else None + condition = condition_data if condition_data is not None else '' + + # Parse data for statements + statement_match = re.search(r"(?:DO\s+)(?:INSTEAD\s+)?((.|\n)*)", data_def) + statement_data = statement_match.group(1) if statement_match else None + statement = statement_data if statement_data is not None else '' + + # set columns parse data + res_data['event'] = event.lower().capitalize() + res_data['do_instead'] = instead + res_data['statements'] = statement + res_data['condition'] = condition + except Exception as e: + return internal_server_error(errormsg=str(e)) + return res_data diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py new file mode 100644 index 0000000..79a03a9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py @@ -0,0 +1,426 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases.schemas.views as views +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from ..parse_definition import parse_definition + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str,bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class RuleModule(CollectionNodeModule): + NODE_TYPE = 'rule' + COLLECTION_LABEL = gettext("Rules") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(RuleModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that scid is catalog and schema object + # and catalog name is 'sys', 'dbo', 'information_schema' then only we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs then allow us to + make dission if we want to load this module or not for that schema + """ + if super(RuleModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + template_path = 'rules/sql' + SQL = render_template("/".join([template_path, 'backend_support.sql']), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', 'information_schema', + # then False (Do not load this module), othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid, vid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(vid) + + @property + def script_load(self): + """ + Load the module script for rule, when any of the database node is + initialized. + """ + return views.ViewModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "rules/css/rule.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +blueprint = RuleModule(__name__) + + +class RuleView(PGChildNodeView): + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'vid'} + ] + ids = [ + {'type': 'int', 'id': 'rid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "rules/js/rules.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running rule, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + ver = self.manager.version + self.template_path = 'rules/sql' + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, vid): + """ + It fetches all rules properties and render into properties + tab + """ + # fetch schema name by schema id + SQL = render_template("/".join([self.template_path, 'properties.sql']), vid=vid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, vid): + """ + It lists down the all rules under the Rules Collection node + """ + res = [] + SQL = render_template("/".join([self.template_path, 'properties.sql']), vid=vid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + row['name'], + icon="icon-rule" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, vid, rid): + """ + It fetches the properties of an individual rule + and render in properties tab + + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), rid=rid, datlastsysoid = self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return ajax_response( + response=parse_definition(res), + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, vid): + """ + This function will creates new the rule object + """ + required_args = [ + 'name', + ] + + data = request.form if request.form else json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join([self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + # It fetches rule id against rule name to display node in tree browser + SQL = render_template("/".join([self.template_path, 'rule_id.sql']), rule_name=data['name']) + status, rule_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=rule_id) + return jsonify( + node=self.blueprint.generate_browser_node( + rule_id, + data['name'], + icon="icon-rule" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid, rid): + """ + This function will update rule object + """ + data = request.form if request.form else json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid, rid) + try: + if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Rule updated", + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid, rid): + """ + This function will drop the rule object + """ + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + # Get name for rule from did + SQL = render_template("/".join([self.template_path, 'delete.sql']), rid=rid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=name) + # drop rule + rset = res_data['rows'][0] + SQL = render_template("/".join([self.template_path, 'delete.sql']), + rulename=rset['rulename'], + relname=rset['relname'], + nspname=rset['nspname'], + cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid, rid=None): + """ + This function to return modified SQL + """ + data = request.args + SQL = self.getSQL(gid, sid, data, vid, rid) + if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data="--modified SQL", + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, vid, rid): + """ + This function will generate sql to render into sql panel + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + res_data = parse_definition(res) + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=res_data, display_comments=True) + + return ajax_response(response=SQL) + + def getSQL(self, gid, sid, data, vid, rid=None): + """ + This function will genrate sql from model data + """ + try: + if rid is not None: + SQL = render_template("/".join([self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + res_data = [] + res_data = parse_definition(res) + if not status: + return internal_server_error(errormsg=res) + old_data = res_data + SQL = render_template( + "/".join([self.template_path,'update.sql']), + data=data, o_data=old_data + ) + else: + SQL = render_template("/".join([self.template_path, 'create.sql']), data=data) + return SQL + except Exception as e: + return internal_server_error(errormsg=str(e)) + +RuleView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe6ed27d98351a03e98451fe83e785aeb51351f GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Q-Dv1E0BJDrt0oW+5b=H{C_&@ z|C4F|pG^AyxbOed8ULTo{{Ljk|Hl*lKkj*b(Es0~_J5B$|2=B`_pssL!=`@^>;65g z`S+m8Y=`zEpec+cL4Lsu4$p3+0Xdun9+AaB+5?Q;PG;Ky8Bv}tjv*44r}laCH7M}7 zJY3k(rBU~;KGd5<=#Js+WtXq}O?@?enTE6ko10E>S;3Z`GiP+(9r|ThaVJc2?wlhg z<wBFqmar9VnEKaN=E$TcORXQ5+JBvSzqq!FQQCtkVR^CB37{>iC9V-ADTyViR>?)F zK#IZ0z|cU~&`8(7FvQ5f%EZ{p#8lhB)XKnM-aEZjC>nC}Q!>*kAsP%U!5V<7O{@&e WAR10h4_yP)z~JfX=d#Wzp$P!7cZ;e3 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4978090e33bbe3d5f7ed249fd93c2da34f52d6 GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}cz{ocE0DgsQug(ks{c>t{C_(0 z|C1^IANT%y)b{UT{r{)4{y&-e|8f7nNA3R}HvW51{r~Cg|4*j>e>~yeqt1U1oBlnh z`S+mw|I-=&pG^AyxclFumVXax|2?Sq_n>kK%jYzp(TpWQe!&b5&u)M?oCO|{#X#Bv zjNMLV+W{G&o-U3d5|`KZnTs_T@Hi`bDEDUl`g^`}GN)YjKDBz@O#-F|X1Becro3=L zVnUz`J9~da#}rq6_MWOkIpP6gt8f32>#JDS-4zfcy)J)|(ya4dQ}_S3nP9`r$iw)~ zA83JUiEBhjN@7W>RdP`(kYX@0Ff`CLG}1LN3^6jWGBLI?G1WFOwK6c6_fBsWiiX_$ ml+3hBhz0{oum+%N6DtEVh=x<sL)QQ`FnGH9xvX<aXaWEjK9-vR literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css new file mode 100644 index 0000000..d7e6750 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css @@ -0,0 +1,9 @@ +.icon-rule{ + background-image: url('{{ url_for('NODE-rule.static', filename='img/rule.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js new file mode 100644 index 0000000..ca5354c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js @@ -0,0 +1,186 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'codemirror'], +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + if (!pgBrowser.Nodes['coll-rule']) { + var rules = pgAdmin.Browser.Nodes['coll-rule'] = + pgAdmin.Browser.Collection.extend({ + node: 'rule', + label: '{{ _('Rules') }}', + type: 'coll-rule', + columns: ['name', 'owner', 'comment'] + }); + }; + + if (!pgBrowser.Nodes['rule']) { + pgAdmin.Browser.Nodes['rule'] = pgAdmin.Browser.Node.extend({ + parent_type: ['view'], + type: 'rule', + label: '{{ _('rule') }}', + collection_type: 'coll-rule', + hasSQL: true, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_rule_on_coll', node: 'coll-rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon pg-icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon pg-icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon pg-icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + } + ]); + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', + type: 'text', disabled: function(m){ + return !m.isNew(); + } + },{ + id: 'oid', label:'{{ _('OID') }}', + type: 'text', disabled: true + },{ + id: 'schema', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + // It is used while generating sql + m.set('schema', m.node_info.schema.label); + } + },{ + id: 'view', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + // It is used while generating sql + m.set('view', m.node_info.view.label); + } + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', type: 'multiline' + },{ + id: 'event', label:'{{ _('Event') }}', control: 'select2', + group: 'Definition', type: 'text', + select2: { + width: '100%' + }, + options:[ + {label: 'Select', value: 'Select'}, + {label: 'Insert', value: 'Insert'}, + {label: 'Update', value: 'Update'}, + {label: 'Delete', value: 'Delete'} + ] + },{ + id: 'do_instead', label:'{{ _('Do Instead') }}', group: 'Definition', + type: 'switch', 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + },{ + id: 'condition', label:'{{ _('Condition') }}', + type: 'text', group: 'Definition', + control: 'sql-field' + },{ + id: 'statements', label:'{{ _('Definition') }}', + type: 'text', group: 'Statements', control: 'sql-field' + },{ + id: 'system_rule', label:'{{ _('System rule?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + },{ + id: 'enabled', label:'{{ _('Enabled?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + } + ], + validate: function() { + /* + * Triggers specific error messages for fields + */ + var err = {}, + errmsg, + field_name = this.get('name'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + return null; + } + }), + canCreate: function(itemData, item, data) { + //If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-rule' == d._type) { + //Check if we are not child of rule + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + prev_k = t.hasParent(prev_j) ? t.parent(prev_j) : null; + prev_f = prev_k ? t.itemData(prev_k) : null; + if( prev_f._type == 'catalog') { + return false; + } else { + return true; + } + } + //Check if it is view and its parent node is schema + //then allow to create Rule + else if('view' == d._type){ + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + if(prev_e._type == 'schema') { + return true; + }else{ + return false; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // by default we do not want to allow create menu + return true; + + } + + }); + } + + return pgBrowser.Nodes['coll-rule']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql new file mode 100644 index 0000000..3c7436f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql @@ -0,0 +1,26 @@ +{#============Create Rule=============#} +{% if display_comments %} +-- Rule: {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP Rule {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.view %} +CREATE OR REPLACE RULE {{ conn|qtIdent(data.name) }} AS + ON {{ data.event|upper if data.event else 'SELECT' }} TO {{ conn|qtIdent(data.schema, data.view) }} +{% if data.condition %} + WHERE {{ data.condition }} +{% endif %} + DO{% if data.is_instead == True %} +{{ ' INSTEAD' }} + {% else %} +{{ ' ' }} + {% endif %} +{% if data.statements %} +{{ data.statements.rstrip(';') }}; +{% else %} +NOTHING; +{% endif %} +{% if data.comment %} +COMMENT ON RULE {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql new file mode 100644 index 0000000..3fa3831 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql @@ -0,0 +1,11 @@ +{#======== Drop/Cascade Rule =========#} +{% if rid %} +SELECT rw.rulename, cl.relname, nsp.nspname + FROM pg_rewrite rw + JOIN pg_class cl ON cl.oid=rw.ev_class + JOIN pg_namespace nsp ON nsp.oid=cl.relnamespace + WHERE rw.oid={{ rid }}; +{% endif %} +{% if rulename and relname and nspname %} +DROP RULE {{ conn|qtIdent(rulename) }} ON {{ conn|qtIdent(nspname, relname) }} {% if cascade %} CASCADE {% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql new file mode 100644 index 0000000..a237250 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql @@ -0,0 +1,21 @@ +{#===================Fetch Rules====================#} +{% if vid or rid %} +SELECT rw.oid AS oid, rw.rulename AS name, rw.*, relname AS view, + CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname AS schema, description AS comment, + {#==Checks whether it is system rule or not===#} + CASE WHEN rw.rulename = '_RETURN' THEN True ELSE False END AS system_rule, + CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled, + pg_get_ruledef(rw.oid, true) AS definition + FROM pg_rewrite rw + JOIN pg_class cl ON cl.oid=rw.ev_class + JOIN pg_namespace nsp ON nsp.oid=cl.relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=rw.oid AND des.classoid='pg_rewrite'::regclass) + WHERE + {% if vid %} + ev_class = {{ vid }} + {% elif rid %} + rw.oid = {{ rid }} + {% endif %} + ORDER BY rw.rulename + {% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql new file mode 100644 index 0000000..53a84ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql @@ -0,0 +1,4 @@ +{#========Below will provide rule id for last created rule========#} +{% if rule_name %} +SELECT rw.oid FROM pg_rewrite rw WHERE rw.rulename={{ rule_name|qtLiteral }} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql new file mode 100644 index 0000000..e2c3aed --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql @@ -0,0 +1,22 @@ +{#============Update Rule=============#} +{% if data.event or data.do_instead or data.condition %} +CREATE OR REPLACE RULE {{ conn|qtIdent(o_data.name) }} AS + ON {% if data.event and data.event != o_data.event %}{{ data.event|upper }}{% else %}{{ o_data.event|upper }}{% endif %} + TO {{ conn|qtIdent(o_data.schema, o_data.view) }} +{% if data.condition and o_data.condition != data.condition %} + WHERE {{ data.condition }} +{% elif data.condition is not defined and o_data.condition %} + WHERE {{ o_data.condition }} +{% endif %} + DO{% if data.do_instead in ['true', True] %}{{ ' INSTEAD' }}{% else %}{{ ' ' }} +{% endif %} +{% if data.statements and data.statements != o_data.statements %} + {{ data.statements.rstrip(';') }}; + {% elif data.statements is not defined and o_data.statements %} + {{ o_data.statements.rstrip(';') }}; +{% else %} + NOTHING; +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON RULE {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.schema, o_data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png new file mode 100644 index 0000000000000000000000000000000000000000..078d533684b8bd7e185749c4f1c9620af23d114b GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}Y=BRQE08|>xOdKRnRzD^9{rj2 z_}lVF@Af@;b@}P@d(WRgefI3xqo=p7KVP}!V)Uc`-T(gmd-%V5`%AU||Nn1)p?>Gh zn(Z&tPJa$N{=xgfzqUJn>TdojJ^s=A@Oy`SZ_IYR((AHW{03+NV@Z%-FoVOh8)+a; zlDE5yQ1zZ1Ae(`+z$3C4NPB>>+sSM@AS2(?#W6(Ua%g{~P=f-Gv+27P9V>QJ|4;U6 zbe!GKH+kPSMNh%0YD;wVPp!&p$=H2Qa>4_iC-Ws`4oVu`kXvDIe5l_eV|jwW;+5+S zYgy6~PYdy+-cS45)~k2Z!$WAAUct+ok^krC+MlVs+ppfyc`@F`nC<>l-VMq?t5r)} zBT7;dOH!?pi&B9UgOP!up{{|Eu910&p^25DrIo3vwt<<IfkFPhkew(Ra`RI%(<*Um UkbC@w6{vy1)78&qol`;+0LQMW5dZ)H literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png new file mode 100644 index 0000000000000000000000000000000000000000..44116db56496bb477c8ece7d3fdda666e1b3758b GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE08|>xOdKRnaBToAO4>7 z;On9XZ+AU@dGYD<`_G?0efI3x<EOW7JYRF{Me@Q^nvec>Z-1%w|NsB(FVt^;p7P|y zjoU9bT=|i6;cMdQ&tb<tdSCgTbKzU!@sHlyU#LC&-+lap_k(|JcmCAf{8f7ONA}_O z4*TAi?Rus6RdrP&&|=1tAirP+hi5m^K%69RcNf7&TWxm%Ih+L^k;OpT1B~5HX4?T7 zS)MMAArhC96Am!+_;@g=95{03(5YkR4xT)E^z7l&>_QLRH3SlxI5?abofwq#PpGhn z3y3`waupO1o<6}vaKfZ%6BvUwB`{h}XxPzW6T>ijp+R<{)&<#R3sx-A(NkQ!Xcb#n z<0DHqpYzNNJzLm||5$yx0JK%L#5JNMC9x#cD!C{XNHG{07#ipr8tEDsh8P)GnHXD{ zm}(oCS{WG3d#ASwMMG|WN@iLmM1z4PSOZYCiIssFM8m1+p=*E|7(8A5T-G@yGywnx Cm9(({ literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css new file mode 100644 index 0000000..ca33ab3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css @@ -0,0 +1,9 @@ +.pg-icon-view{ + background-image: url('{{ url_for('NODE-view.static', filename='img/view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js new file mode 100644 index 0000000..4cc263c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js @@ -0,0 +1,238 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + var SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + securitylabel: undefined + }, + schema: [{ + id: 'provider', label: '{{ _('Provider') }}', + type: 'text', editable: true + },{ + id: 'security_label', label: '{{ _('Security Label') }}', + type: 'text', editable: true + }], + validate: function() { + var err = {}, + errmsg = null, + field_name = this.get('provider'), + field_security_label = this.get('security_label'); + + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['provider'] = '{{ _("Provider cannot be empty.") }}'; + errmsg = errmsg || err['provider']; + this.errorModel.set('provider', errmsg); + return errmsg; + }else{ + this.errorModel.unset('provider'); + } + + if (_.isUndefined(field_security_label) || _.isNull(field_security_label) || + String(field_security_label).replace(/^\s+|\s+$/g, '') == '') { + err['security_label'] = '{{ _("Security Label cannot be empty.") }}'; + errmsg = errmsg || err['security_label']; + this.errorModel.set('security_label', errmsg); + return errmsg; + }else{ + this.errorModel.unset('security_label'); + } + return null; + } + }); + + if (!pgBrowser.Nodes['coll-view']) { + var views= pgAdmin.Browser.Nodes['coll-view'] = + pgAdmin.Browser.Collection.extend({ + node: 'view', + label: '{{ _('Views') }}', + type: 'coll-view', + columns: ['name', 'owner'] + }); + }; + + if (!pgBrowser.Nodes['view']) { + pgAdmin.Browser.Nodes['view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'view', + label: '{{ _('View') }}', + hasSQL: true, + collection_type: 'coll-view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_view_on_coll', node: 'coll-view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon pg-icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon pg-icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon pg-icon-view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text' + },{ + id: 'oid', label:'{{ _('Oid') }}', cell: 'string', + type: 'text', disabled: true + },{ + id: 'owner', label:'{{ _('Owner') }}', cell: 'string', control: 'node-list-by-name', + node: 'role' + },{ + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', type: 'multiline' + },{ + id: 'system_view', label:'{{ _('System view?') }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + },{ + id: 'security_barrier', label:'{{ _('Security Barrier') }}', cell: 'string', + type: 'switch', min_version: '90200', + group: 'Definition', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + },{ + id: 'check_option', label:'{{ _('Check Options') }}', control: 'select2', + group: 'Definition', type: 'text', min_version: '90400', mode:['create', 'edit'], + select2: { + width: '100%' + }, + options:[ + {label: "No", value: "no"}, + {label: "Local", value: "local"}, + {label: "Cascaded", value: "cascaded"} + ] + },{ + id: 'definition', label:'{{ _('Definition') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], group: 'Definition', + control: 'sql-field' + },{ + id: 'm_view', label:'{{ _('Materialized view?') }}', cell: 'string', + type: 'switch', mode: ['properties'], min_version: '90300', + group: 'Materialization', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + },{ + id: 'seclabels', label: '{{ _('Securitiy Labels') }}', model: SecurityModel, + editable: false, type: 'collection', canEdit: false, + group: '{{ _('Security') }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + },{ + id: 'datacl', label: '{{ _('Privileges') }}', model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _('Security') }}', mode: ['properties', 'edit', 'create'], + canAdd: true, canDelete: true, control: 'unique-col-collection', + } + ], + validate: function() { + /* + * Triggers specific error messages for fields + */ + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + } + }), + canCreate: function(itemData, item, data) { + //If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-view' == d._type) { + //Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql new file mode 100644 index 0000000..f236e40 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql @@ -0,0 +1,30 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{vid}}) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql new file mode 100644 index 0000000..a88a838 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql new file mode 100644 index 0000000..2fd9ad1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql @@ -0,0 +1,29 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql new file mode 100644 index 0000000..8eba9df --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql new file mode 100644 index 0000000..e66b0cb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql @@ -0,0 +1,30 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql new file mode 100644 index 0000000..a88a838 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql new file mode 100644 index 0000000..3440e89 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql @@ -0,0 +1,36 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, c.relacl, description As comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql new file mode 100644 index 0000000..8eba9df --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql new file mode 100644 index 0000000..8f2a4c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql @@ -0,0 +1,28 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql new file mode 100644 index 0000000..a88a838 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql new file mode 100644 index 0000000..b906475 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql @@ -0,0 +1,60 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, c.relkind, nsp.nspname AS schema, + c.relispopulated AS ispopulated, spc.spcname, pg_get_userbyid(c.relowner) + AS owner, c.relacl, description, pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql new file mode 100644 index 0000000..01b2357 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql @@ -0,0 +1,82 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql new file mode 100644 index 0000000..8f2a4c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql @@ -0,0 +1,28 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql new file mode 100644 index 0000000..552adf9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql @@ -0,0 +1,62 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql new file mode 100644 index 0000000..e286fe0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql new file mode 100644 index 0000000..8f2a4c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql @@ -0,0 +1,28 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql new file mode 100644 index 0000000..552adf9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql @@ -0,0 +1,62 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql new file mode 100644 index 0000000..e286fe0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql new file mode 100644 index 0000000..e66b0cb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql @@ -0,0 +1,30 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, +array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql new file mode 100644 index 0000000..cd49223 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql new file mode 100644 index 0000000..ec966b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql new file mode 100644 index 0000000..552adf9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql @@ -0,0 +1,62 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql new file mode 100644 index 0000000..e286fe0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-02-24 13:31 Surinder Kumar <[email protected]> parent: Surinder Kumar <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Surinder Kumar @ 2016-02-24 13:31 UTC (permalink / raw) To: pgadmin-hackers Hi, Please find the updated patch with following changes: 1. Integrated dependents and dependency tabs with view and rule node. 2. Added "Create Rule" option for Table node. 3. Added node_inode property for rule node as it has no child node. 4. Improved javascript code commenting and added pydoc in python files. 5. Name column under dependents tab showing wrong name for rule node due to typo mistake, It is fixed. Please review it and Let me know for any comments. On Tue, Feb 2, 2016 at 1:59 PM, Surinder Kumar < [email protected]> wrote: > Hi, > > > Please find the patch for the View and its child node Rule. > You have to apply database node and SqlFieldControl patch to run it. > > Please review it and Let me know for any comments. > > > Regards, > Surinder Kumar > -- Sent via pgadmin-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers Attachments: [application/octet-stream] view_and_role_node_v2.patch (167.4K, 3-view_and_role_node_v2.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py new file mode 100644 index 0000000..fb4d692 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -0,0 +1,727 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements View Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases.schemas as schemas +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from .parse_definition import parse_definition + + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str, bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class ViewModule(CollectionNodeModule): + """ + class ViewModule(Object): + + A view collection Node which inherits CollectionNodeModule + class and define methods: + get_nodes - To generate collection node. + script_load - tells when to load js file. + csssnppets - add css to page + """ + NODE_TYPE = 'view' + COLLECTION_LABEL = gettext("Views") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(ViewModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that scid is catalog \ + # and schema object and catalog name is 'sys', 'dbo', + # 'information_schema' then only we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs \ + then allow us to make dission if we want to load this module or \ + not for that schema + """ + if super(ViewModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + # we will set template path for sql scripts + if server_type == 'ppas' and ver >= 90100: + template_path = 'templates/views/sql/edb_9.1_plus' + elif ver >= 90100: + template_path = 'templates/views/sql/9.4_plus' + else: + # Note: View is not supported below postgres version 9.1 + # Hence we will not load this module + return False + + SQL = render_template("/".join([ + template_path, 'backend_support.sql']), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', \ + # 'information_schema',then False (Do not load this module), \ + # othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for view, when any of the database node is + initialized. it is because views are under catalogs and schemas . + """ + return schemas.SchemaModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "templates/views/css/view.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + +# Set template_folder to views,as view also uses rules sql templates. +blueprint = ViewModule(__name__, template_folder="../views") + + +class ViewNode(PGChildNodeView): + """ + It is a class for view node which inherits the + properties and methods from PGChildNodeView class and define + various methods to list, create, update and delete view. + + Variables: + --------- + * node_type - tells which type of node it is + * parent_ids - id with its type and name of parent nodes + * ids - id with type and name of extension module being used. + * operations - function routes mappings defined. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'vid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "templates/views/js/views.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[ + kwargs['did']]['datlastsysoid'] + ver = self.manager.version + + # we will set template path for sql scripts + if ver >= 90400: + self.template_path = 'templates/views/sql/9.4_plus' + elif ver >= 90300: + self.template_path = 'templates/views/sql/9.3_plus' + elif ver >= 90200: + self.template_path = 'templates/views/sql/9.2_plus' + elif ver >= 90100: + self.template_path = 'templates/views/sql/9.1_plus' + else: + self.template_path = 'templates/views/sql/pre_9.1' + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid): + """ + It fetches all views properties and render into properties + tab + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), scid=scid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + It lists down the all views under the Views Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + scid, + row['name'], + icon="icon-view" + )) + + return make_json_response( + data=res, + status=200 + ) + + def parse_views_privileges(self, db_privileges): + """ + This function forms separate list of grantable + and non grantable privileges. + """ + acl = {'grantor': db_privileges['grantor'], + 'grantee': db_privileges['grantee'], + 'privileges': [] + } + + privileges = [] + for idx, priv in enumerate(db_privileges['privileges']): + if db_privileges['grantable'][idx]: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": True + }) + else: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": False + }) + + acl['privileges'] = privileges + + return acl + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + It fetches the properties of an individual view + and render in properties tab + + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join( + [self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + return ajax_response( + response=result, + status=200 + ) + + @staticmethod + def formatter(result): + """ This function formats output for + security label & variables""" + frmtd_result = dict() + sec_lbls = [] + if 'seclabels' in result and result['seclabels'] is not None: + for sec in result['seclabels']: + import re + sec = re.search(r'([^=]+)=(.*$)', sec) + sec_lbls.append({ + 'provider': sec.group(1), + 'security_label': sec.group(2) + }) + + frmtd_result.update({"seclabels": sec_lbls}) + return frmtd_result + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will creates new view object + """ + required_args = [ + 'name', + 'definition' + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join( + [self.template_path, 'view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=view_id) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid): + """ + This function will update view object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid) + try: + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View updated"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info=gettext("Nothing to update"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid): + """ + This function will drop the view object + """ + + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + + # Get name for view from did + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), vid=vid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + + # drop view + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), + nspname=res_data['rows'][0]['nspname'], + name=res_data['rows'][0]['name'], cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid=None): + """ + This function to return modified SQL + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + SQL = self.getSQL(gid, sid, data, vid) + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("--modified SQL"), + status=200 + ) + + @staticmethod + def parse_privileges(str_privileges, object_type='VIEW'): + """Parse Privileges.""" + db_privileges = { + 'a': 'INSERT', + 'r': 'SELECT', + 'w': 'UPDATE', + 'd': 'DELETE', + 'x': 'REFERENCE', + 't': 'TRIGGER' + } + privileges = [] + for priv in str_privileges: + priv_with_grant = [] + priv_without_grant = [] + for privilege in priv['privileges']: + if privilege['with_grant']: + priv_with_grant.append( + db_privileges[privilege['privilege_type']]) + elif privilege['privilege']: + priv_without_grant.append( + db_privileges[privilege['privilege_type']]) + priv_with_grant = ", ".join(priv_with_grant) + priv_without_grant = ", ".join(priv_without_grant) + + # Server Level validation + if 'grantee' in priv: + privileges.append( + { + 'grantee': priv['grantee'] if 'grantee' in priv else '', + 'with_grant': priv_with_grant, + 'without_grant': priv_without_grant + } + ) + else: + return '' + return privileges + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will genrate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + SQL = render_template("/".join( + [self.template_path, 'update.sql']), data=data, + o_data=old_data, conn=self.conn) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def sql(self, gid, sid, did, scid, vid): + """ + This function will generate sql to render into sql panel + """ + SQL_data = '' + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch all privileges for view + SQL = render_template("/".join( + [self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0]['datacl'].append(priv) + else: + res['rows'][0]['datacl'] = [priv] + + result = res['rows'][0] + if 'datacl' in result: + result['datacl'] = self.parse_privileges(result['datacl']) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), + data=result, + conn=self.conn, + display_comments=True + ) + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'grant.sql']), data=result) + + SQL_data += SQL + + """Get all non system rules of view node, generate their sql and render + into sql tab""" + self.temp_path = 'rules/templates/rules/sql' + SQL = render_template("/".join( + [self.temp_path, 'properties.sql']), vid=vid) + status, data_prop = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for rule in data_prop['rows']: + + # Generate SQL only for non system rule + if rule['name'] != '_RETURN': + res = [] + SQL = render_template("/".join( + [self.temp_path, 'properties.sql']), + rid=rule['oid'] + ) + status, res = self.conn.execute_dict(SQL) + res_data = parse_definition(res) + SQL = render_template("/".join( + [self.temp_path, 'create.sql']), + data=res_data, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + + return ajax_response(response=SQL_data) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid): + """ + This function get the dependents and return ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependents_result = self.get_dependents(self.conn, vid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid): + """ + This function get the dependencies and return ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependencies_result = self.get_dependencies(self.conn, vid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +ViewNode.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py new file mode 100644 index 0000000..f317acf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py @@ -0,0 +1,59 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Parse definition field data """ + +from pgadmin.utils.ajax import internal_server_error + + +def parse_definition(res): + """ + This function extracts: + - events + - do_instead + - statements + - condition + from the defintion row, form an array with + fields and return it. + """ + res_data = [] + try: + res_data = res['rows'][0] + data_def = res_data['definition'] + import re + # Parse data for event + e_match = re.search(r"ON\s+(.*)\s+TO", data_def) + event_data = e_match.group(1) if e_match is not None else None + event = event_data if event_data is not None else '' + + # Parse data for do instead + inst_match = re.search(r"\s+(INSTEAD)\s+", data_def) + instead_data = inst_match.group(1) if inst_match is not None else None + instead = True if instead_data is not None else False + + # Parse data for condition + condition_match = re.search(r"(?:WHERE)\s+(.*)\s+(?:DO)", data_def) + condition_data = condition_match.group(1) \ + if condition_match is not None else None + condition = condition_data if condition_data is not None else '' + + # Parse data for statements + statement_match = re.search( + r"(?:DO\s+)(?:INSTEAD\s+)?((.|\n)*)", data_def) + statement_data = statement_match.group(1) if statement_match else None + statement = statement_data if statement_data is not None else '' + + # set columns parse data + res_data['event'] = event.lower().capitalize() + res_data['do_instead'] = instead + res_data['statements'] = statement + res_data['condition'] = condition + except Exception as e: + return internal_server_error(errormsg=str(e)) + return res_data diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py new file mode 100644 index 0000000..2d958e2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py @@ -0,0 +1,526 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Rule Node """ + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases.schemas.views as views +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from ..parse_definition import parse_definition + + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str, bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class RuleModule(CollectionNodeModule): + """ + class RuleModule(Object): + + A rule collection Node which inherits CollectionNodeModule + class and define methods: + get_nodes - To generate collection node. + script_load - tells when to load js file. + csssnppets - add css to page + """ + NODE_TYPE = 'rule' + COLLECTION_LABEL = gettext("Rules") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(RuleModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that scid is catalog \ + # and schema object and catalog name is 'sys', 'dbo', + # 'information_schema' then only we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs \ + then allow us to make dission if we want to load this module or \ + not for that schema + """ + if super(RuleModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + template_path = 'rules/sql' + SQL = render_template("/".join( + [template_path, 'backend_support.sql'] + ), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', \ + # 'information_schema',then False (Do not load this module), \ + # othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid, vid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(vid) + + @property + def node_inode(self): + """ + If a node have child return True otherwise False + """ + return False + + @property + def script_load(self): + """ + Load the module script for rule, when any of the database node is + initialized. + """ + return views.ViewModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "rules/css/rule.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +# Create blueprint of RuleModule. +blueprint = RuleModule(__name__) + + +class RuleView(PGChildNodeView): + """ + It is a class for rule node which inherits the + properties and methods from PGChildNodeView class and define + various methods to list, create, update and delete rule. + + Variables: + --------- + * node_type - tells which type of node it is + * parent_ids - id with its type and name of parent nodes + * ids - id with type and name of extension module being used. + * operations - function routes mappings defined. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'vid'} + ] + ids = [ + {'type': 'int', 'id': 'rid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "rules/js/rules.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running rule, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + ver = self.manager.version + self.template_path = 'rules/sql' + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, vid): + """ + It fetches all rules properties and render into properties + tab + """ + + # fetch schema name by schema id + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), vid=vid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, vid): + """ + It lists down the all rules under the Rules Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), vid=vid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + vid, + row['name'], + icon="icon-rule" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, vid, rid): + """ + It fetches the properties of an individual rule + and render in properties tab + + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql'] + ), rid=rid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return ajax_response( + response=parse_definition(res), + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, vid): + """ + This function will creates new the rule object + """ + required_args = [ + 'name', + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + """ It fetches rule id against rule name to display node + in tree browser""" + SQL = render_template("/".join( + [self.template_path, 'rule_id.sql']), rule_name=data['name']) + status, rule_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=rule_id) + return jsonify( + node=self.blueprint.generate_browser_node( + rule_id, + vid, + data['name'], + icon="icon-rule" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid, rid): + """ + This function will update rule object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid, rid) + try: + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule updated"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info=gettext("Nothing to update"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid, rid): + """ + This function will drop the rule object + """ + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + # Get name for rule from did + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), rid=rid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + # drop rule + rset = res_data['rows'][0] + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), + rulename=rset['rulename'], + relname=rset['relname'], + nspname=rset['nspname'], + cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid, rid=None): + """ + This function to return modified SQL + """ + data = request.args + SQL = self.getSQL(gid, sid, data, vid, rid) + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("--modified SQL"), + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, vid, rid): + """ + This function will generate sql to render into sql panel + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + res_data = parse_definition(res) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), + data=res_data, display_comments=True) + + return ajax_response(response=SQL) + + def getSQL(self, gid, sid, data, vid, rid): + """ + This function will genrate sql from model data + """ + try: + if rid is not None: + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + res_data = [] + res_data = parse_definition(res) + if not status: + return internal_server_error(errormsg=res) + old_data = res_data + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data + ) + else: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + return SQL + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid, rid): + """ + This function get the dependents and return ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + rid: Rule ID + """ + dependents_result = self.get_dependents(self.conn, rid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid, rid): + """ + This function get the dependencies and return ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + rid: Rule ID + """ + dependencies_result = self.get_dependencies(self.conn, rid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +RuleView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe6ed27d98351a03e98451fe83e785aeb51351f GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Q-Dv1E0BJDrt0oW+5b=H{C_&@ z|C4F|pG^AyxbOed8ULTo{{Ljk|Hl*lKkj*b(Es0~_J5B$|2=B`_pssL!=`@^>;65g z`S+m8Y=`zEpec+cL4Lsu4$p3+0Xdun9+AaB+5?Q;PG;Ky8Bv}tjv*44r}laCH7M}7 zJY3k(rBU~;KGd5<=#Js+WtXq}O?@?enTE6ko10E>S;3Z`GiP+(9r|ThaVJc2?wlhg z<wBFqmar9VnEKaN=E$TcORXQ5+JBvSzqq!FQQCtkVR^CB37{>iC9V-ADTyViR>?)F zK#IZ0z|cU~&`8(7FvQ5f%EZ{p#8lhB)XKnM-aEZjC>nC}Q!>*kAsP%U!5V<7O{@&e WAR10h4_yP)z~JfX=d#Wzp$P!7cZ;e3 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4978090e33bbe3d5f7ed249fd93c2da34f52d6 GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}cz{ocE0DgsQug(ks{c>t{C_(0 z|C1^IANT%y)b{UT{r{)4{y&-e|8f7nNA3R}HvW51{r~Cg|4*j>e>~yeqt1U1oBlnh z`S+mw|I-=&pG^AyxclFumVXax|2?Sq_n>kK%jYzp(TpWQe!&b5&u)M?oCO|{#X#Bv zjNMLV+W{G&o-U3d5|`KZnTs_T@Hi`bDEDUl`g^`}GN)YjKDBz@O#-F|X1Becro3=L zVnUz`J9~da#}rq6_MWOkIpP6gt8f32>#JDS-4zfcy)J)|(ya4dQ}_S3nP9`r$iw)~ zA83JUiEBhjN@7W>RdP`(kYX@0Ff`CLG}1LN3^6jWGBLI?G1WFOwK6c6_fBsWiiX_$ ml+3hBhz0{oum+%N6DtEVh=x<sL)QQ`FnGH9xvX<aXaWEjK9-vR literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css new file mode 100644 index 0000000..d2b036a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css @@ -0,0 +1,8 @@ +.icon-rule{ + background-image: url('{{ url_for('NODE-rule.static', filename='img/rule.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js new file mode 100644 index 0000000..652c80d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js @@ -0,0 +1,252 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + + /** + Create and add a rule collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-rule']) { + var rules = pgAdmin.Browser.Nodes['coll-rule'] = + pgAdmin.Browser.Collection.extend({ + node: 'rule', + label: '{{ _('Rules') }}', + type: 'coll-rule', + columns: ['name', 'owner', 'comment'] + }); + }; + + + /** + Create and Add an View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['rule']) { + pgAdmin.Browser.Nodes['rule'] = pgAdmin.Browser.Node.extend({ + parent_type: ['view'], + type: 'rule', + label: '{{ _('rule') }}', + collection_type: 'coll-rule', + hasSQL: true, + hasDepends: true, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create rule" menu option into context and object menu + for the following nodes: + coll-rule, rule and view and table. + @property {data} - Allow create rule option on schema node or + system rules node. + */ + pgBrowser.add_menus([{ + name: 'create_rule_on_coll', node: 'coll-rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the rule node and specify the node + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', + type: 'text', disabled: function(m){ + return !m.isNew(); + } + }, + { + id: 'oid', label:'{{ _('OID') }}', + type: 'text', disabled: true + }, + { + id: 'schema', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + + // It is used while generating sql + m.set('schema', m.node_info.schema.label); + } + }, + { + id: 'view', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + + // It is used while generating sql + m.set('view', m.node_info.view.label); + } + }, + { + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', type: 'multiline' + }, + { + id: 'event', label:'{{ _('Event') }}', control: 'select2', + group: 'Definition', type: 'text', + select2: { + width: '100%' + }, + options:[ + {label: 'Select', value: 'Select'}, + {label: 'Insert', value: 'Insert'}, + {label: 'Update', value: 'Update'}, + {label: 'Delete', value: 'Delete'} + ] + }, + { + id: 'do_instead', label:'{{ _('Do Instead') }}', group: 'Definition', + type: 'switch', 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'condition', label:'{{ _('Condition') }}', + type: 'text', group: 'Definition', + control: 'sql-field' + }, + { + id: 'statements', label:'{{ _('Definition') }}', + type: 'text', group: 'Statements', control: 'sql-field' + }, + { + id: 'system_rule', label:'{{ _('System rule?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'enabled', label:'{{ _('Enabled?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + } + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') + { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + } + else + { + this.errorModel.unset('name'); + } + return null; + } + }), + + // Show or hide create rule menu option on parent node + canCreate: function(itemData, item, data) { + + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create rule + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-rule' == d._type) { + + //Check if we are not child of rule + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + prev_k = t.hasParent(prev_j) ? t.parent(prev_j) : null; + prev_f = prev_k ? t.itemData(prev_k) : null; + if( prev_f._type == 'catalog') { + return false; + } else { + return true; + } + } + + /** + Check if it is view and its parent node is schema + then allow to create Rule + */ + else if('view' == d._type){ + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + if(prev_e._type == 'schema') { + return true; + }else{ + return false; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // By default we do not want to allow create menu + return true; + + } + + }); + } + + return pgBrowser.Nodes['coll-rule']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql new file mode 100644 index 0000000..3c7436f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql @@ -0,0 +1,26 @@ +{#============Create Rule=============#} +{% if display_comments %} +-- Rule: {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP Rule {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.view %} +CREATE OR REPLACE RULE {{ conn|qtIdent(data.name) }} AS + ON {{ data.event|upper if data.event else 'SELECT' }} TO {{ conn|qtIdent(data.schema, data.view) }} +{% if data.condition %} + WHERE {{ data.condition }} +{% endif %} + DO{% if data.is_instead == True %} +{{ ' INSTEAD' }} + {% else %} +{{ ' ' }} + {% endif %} +{% if data.statements %} +{{ data.statements.rstrip(';') }}; +{% else %} +NOTHING; +{% endif %} +{% if data.comment %} +COMMENT ON RULE {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql new file mode 100644 index 0000000..7b8318b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql @@ -0,0 +1,16 @@ +{#======== Drop/Cascade Rule =========#} +{% if rid %} +SELECT + rw.rulename, cl.relname, nsp.nspname +FROM + pg_rewrite rw +JOIN + pg_class cl ON cl.oid=rw.ev_class +JOIN + pg_namespace nsp ON nsp.oid=cl.relnamespace +WHERE + rw.oid={{ rid }}; +{% endif %} +{% if rulename and relname and nspname %} +DROP RULE {{ conn|qtIdent(rulename) }} ON {{ conn|qtIdent(nspname, relname) }} {% if cascade %} CASCADE {% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql new file mode 100644 index 0000000..eda1025 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql @@ -0,0 +1,27 @@ +{#===================Fetch Rules====================#} +{% if vid or rid %} +SELECT + rw.oid AS oid, rw.rulename AS name, rw.*, relname AS view, + CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname AS schema, description AS comment, + {#==Checks whether it is system rule or not===#} + CASE WHEN rw.rulename = '_RETURN' THEN True ELSE False END AS system_rule, + CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled, + pg_get_ruledef(rw.oid, true) AS definition +FROM + pg_rewrite rw +JOIN + pg_class cl ON cl.oid=rw.ev_class +JOIN + pg_namespace nsp ON nsp.oid=cl.relnamespace +LEFT OUTER JOIN + pg_description des ON (des.objoid=rw.oid AND des.classoid='pg_rewrite'::regclass) +WHERE + {% if vid %} + ev_class = {{ vid }} + {% elif rid %} + rw.oid = {{ rid }} + {% endif %} +ORDER BY + rw.rulename + {% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql new file mode 100644 index 0000000..3dc6dba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql @@ -0,0 +1,9 @@ +{#========Below will provide rule id for last created rule========#} +{% if rule_name %} +SELECT + rw.oid +FROM + pg_rewrite rw +WHERE + rw.rulename={{ rule_name|qtLiteral }} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql new file mode 100644 index 0000000..b50369a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql @@ -0,0 +1,22 @@ +{#============Update Rule=============#} +{% if data.event or data.do_instead or data.condition %} +CREATE OR REPLACE RULE {{ conn|qtIdent(o_data.name) }} AS + ON {% if data.event and data.event != o_data.event %}{{ data.event|upper }}{% else %}{{ o_data.event|upper }}{% endif %} + TO {{ conn|qtIdent(o_data.schema, o_data.view) }} +{% if data.condition and o_data.condition != data.condition %} + WHERE {{ data.condition }} +{% elif data.condition is not defined and o_data.condition %} + WHERE {{ o_data.condition }} +{% endif %} + DO{% if data.do_instead in ['true', True] %}{{ ' INSTEAD' }}{% else %}{{ ' ' }} +{% endif %} +{% if data.statements and data.statements != o_data.statements %} + {{ data.statements.rstrip(';') }}; + {% elif data.statements is not defined and o_data.statements %} + {{ o_data.statements.rstrip(';') }}; +{% else %} + NOTHING; +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON RULE {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.schema, o_data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png new file mode 100644 index 0000000000000000000000000000000000000000..078d533684b8bd7e185749c4f1c9620af23d114b GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}Y=BRQE08|>xOdKRnRzD^9{rj2 z_}lVF@Af@;b@}P@d(WRgefI3xqo=p7KVP}!V)Uc`-T(gmd-%V5`%AU||Nn1)p?>Gh zn(Z&tPJa$N{=xgfzqUJn>TdojJ^s=A@Oy`SZ_IYR((AHW{03+NV@Z%-FoVOh8)+a; zlDE5yQ1zZ1Ae(`+z$3C4NPB>>+sSM@AS2(?#W6(Ua%g{~P=f-Gv+27P9V>QJ|4;U6 zbe!GKH+kPSMNh%0YD;wVPp!&p$=H2Qa>4_iC-Ws`4oVu`kXvDIe5l_eV|jwW;+5+S zYgy6~PYdy+-cS45)~k2Z!$WAAUct+ok^krC+MlVs+ppfyc`@F`nC<>l-VMq?t5r)} zBT7;dOH!?pi&B9UgOP!up{{|Eu910&p^25DrIo3vwt<<IfkFPhkew(Ra`RI%(<*Um UkbC@w6{vy1)78&qol`;+0LQMW5dZ)H literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png new file mode 100644 index 0000000000000000000000000000000000000000..44116db56496bb477c8ece7d3fdda666e1b3758b GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE08|>xOdKRnaBToAO4>7 z;On9XZ+AU@dGYD<`_G?0efI3x<EOW7JYRF{Me@Q^nvec>Z-1%w|NsB(FVt^;p7P|y zjoU9bT=|i6;cMdQ&tb<tdSCgTbKzU!@sHlyU#LC&-+lap_k(|JcmCAf{8f7ONA}_O z4*TAi?Rus6RdrP&&|=1tAirP+hi5m^K%69RcNf7&TWxm%Ih+L^k;OpT1B~5HX4?T7 zS)MMAArhC96Am!+_;@g=95{03(5YkR4xT)E^z7l&>_QLRH3SlxI5?abofwq#PpGhn z3y3`waupO1o<6}vaKfZ%6BvUwB`{h}XxPzW6T>ijp+R<{)&<#R3sx-A(NkQ!Xcb#n z<0DHqpYzNNJzLm||5$yx0JK%L#5JNMC9x#cD!C{XNHG{07#ipr8tEDsh8P)GnHXD{ zm}(oCS{WG3d#ASwMMG|WN@iLmM1z4PSOZYCiIssFM8m1+p=*E|7(8A5T-G@yGywnx Cm9(({ literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css new file mode 100644 index 0000000..88a2f43 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css @@ -0,0 +1,8 @@ +.icon-view{ + background-image: url('{{ url_for('NODE-view.static', filename='img/view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js new file mode 100644 index 0000000..9a4ebc2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js @@ -0,0 +1,310 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + // Define Security Model with fields and validation + var SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + securitylabel: undefined + }, + schema: [{ + id: 'provider', label: '{{ _('Provider') }}', + type: 'text', editable: true + },{ + id: 'security_label', label: '{{ _('Security Label') }}', + type: 'text', editable: true + }], + + validate: function() { + var err = {}, + errmsg = null, + field_name = this.get('provider'), + field_security_label = this.get('security_label'); + + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['provider'] = '{{ _("Provider cannot be empty.") }}'; + errmsg = errmsg || err['provider']; + this.errorModel.set('provider', errmsg); + return errmsg; + }else{ + this.errorModel.unset('provider'); + } + + if (_.isUndefined(field_security_label) || _.isNull(field_security_label) || + String(field_security_label).replace(/^\s+|\s+$/g, '') == '') { + err['security_label'] = '{{ _("Security Label cannot be empty.") }}'; + errmsg = errmsg || err['security_label']; + this.errorModel.set('security_label', errmsg); + return errmsg; + }else{ + this.errorModel.unset('security_label'); + } + return null; + } + }); + + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-view']) { + var views= pgAdmin.Browser.Nodes['coll-view'] = + pgAdmin.Browser.Collection.extend({ + node: 'view', + label: '{{ _('Views') }}', + type: 'coll-view', + columns: ['name', 'owner'] + }); + }; + + /** + Create and Add an View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['view']) { + pgAdmin.Browser.Nodes['view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'view', + label: '{{ _('View') }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_view_on_coll', node: 'coll-view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text' + }, + { + id: 'oid', label:'{{ _('Oid') }}', cell: 'string', + type: 'text', disabled: true + }, + { + id: 'owner', label:'{{ _('Owner') }}', cell: 'string', control: 'node-list-by-name', + node: 'role' + }, + { + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline' + }, + { + id: 'system_view', label:'{{ _('System view?') }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'security_barrier', label:'{{ _('Security Barrier') }}', cell: 'string', + type: 'switch', min_version: '90200', + group: 'Definition', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'check_option', label:'{{ _('Check Options') }}', + control: 'select2', group: 'Definition', type: 'text', + min_version: '90400', mode:['create', 'edit'], + select2: { + + // set select2 option width to 100% + width: '100%' + }, + options:[ + {label: "No", value: "no"}, + {label: "Local", value: "local"}, + {label: "Cascaded", value: "cascaded"} + ] + }, + { + id: 'definition', label:'{{ _('Definition') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], group: 'Definition', + control: 'sql-field' + }, + { + id: 'm_view', label:'{{ _('Materialized view?') }}', cell: 'string', + type: 'switch', mode: ['properties'], min_version: '90300', + group: 'Materialization', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _('Securitiy Labels') }}', + model: SecurityModel, editable: false, type: 'collection', + canEdit: false, group: '{{ _('Security') }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + }, + + // Add Privilege Control + { + id: 'datacl', label: '{{ _('Privileges') }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _('Security') }}', + mode: ['properties', 'edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', + } + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + } + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql new file mode 100644 index 0000000..807fb04 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{vid}}) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql new file mode 100644 index 0000000..4e290a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql new file mode 100644 index 0000000..9487458 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql @@ -0,0 +1,29 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql new file mode 100644 index 0000000..2a3fbca --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql new file mode 100644 index 0000000..7d19b68 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXIST + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql new file mode 100644 index 0000000..a345e15 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql new file mode 100644 index 0000000..4e290a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql new file mode 100644 index 0000000..3da7801 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql @@ -0,0 +1,39 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, c.relacl, description As comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql new file mode 100644 index 0000000..b60bfa8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql @@ -0,0 +1,30 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql new file mode 100644 index 0000000..ab39cac --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql new file mode 100644 index 0000000..a345e15 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql new file mode 100644 index 0000000..044ff71 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname AS name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql new file mode 100644 index 0000000..f4e3498 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql @@ -0,0 +1,60 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, c.relkind, nsp.nspname AS schema, + c.relispopulated AS ispopulated, spc.spcname, pg_get_userbyid(c.relowner) + AS owner, c.relacl, description, pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql new file mode 100644 index 0000000..f0c4c01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl FROM pg_class cl + LEFT OUTER JOIN + pg_shdescription descr + ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql new file mode 100644 index 0000000..53a9bdb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql @@ -0,0 +1,13 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c +LEFT JOIN + pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql new file mode 100644 index 0000000..fa4dcf6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql @@ -0,0 +1,65 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql new file mode 100644 index 0000000..b5d2225 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql @@ -0,0 +1,33 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql new file mode 100644 index 0000000..5c0648d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql new file mode 100644 index 0000000..f926027 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql @@ -0,0 +1,63 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql new file mode 100644 index 0000000..46c5e5f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql @@ -0,0 +1,32 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql new file mode 100644 index 0000000..61c4a0e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql @@ -0,0 +1,65 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/utils.py b/web/pgadmin/browser/utils.py index 6549851..db44224 100644 --- a/web/pgadmin/browser/utils.py +++ b/web/pgadmin/browser/utils.py @@ -491,7 +491,7 @@ class PGChildNodeView(NodeView): type_name = 'table' elif type_str[0] == 'R': type_name = 'rule' - ref_name = _ref_name + ' ON ' + _ref_name + row['ownertable'] + ref_name = _ref_name + ' ON ' + ref_name + row['ownertable'] _ref_name = None elif type_str[0] == 'C': if type_str[1] == 'c': ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-02-25 11:03 Dave Page <[email protected]> parent: Surinder Kumar <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Dave Page @ 2016-02-25 11:03 UTC (permalink / raw) To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers Hi On Wed, Feb 24, 2016 at 1:31 PM, Surinder Kumar < [email protected]> wrote: > Hi, > > Please find the updated patch with following changes: > > 1. Integrated dependents and dependency tabs with view and rule node. > 2. Added "Create Rule" option for Table node. > 3. Added node_inode property for rule node as it has no child node. > 4. Improved javascript code commenting and added pydoc in python files. > 5. Name column under dependents tab showing wrong name for rule node > due to typo mistake, It is fixed. > > Please review it and Let me know for any comments. > Please find an updated patch attached, in which I tweaked some comments and fixed a few minor issues as I reviewed the code. The following issues need to be addressed please, then I'll take another look: - Don't show the definition SQL in the main properties tab. - Check Options is missing from the properties tab. - Views were made function in PostgreSQL v6.4! Why only load the module for 9.1+? - The TRUNCATE ACL flag is not recognised, leading to ACLs being shown as: arwdUNKNOWNxt - 4 character indents are not applied consistently to SQL templates - Why do servers pre-9.3 have VACUUM options in the SQL templates? - There should be no special casing of Slony schemas (backend_support.sql) - Don't include \'s on line ends of multiline comments or docstrings. - I can't create a Materialised View On the dialogs: - The "Definition" input box should have a border. - The "Definition" input box fill the vertical space assigned to it, such that the border on the left runs top to bottom (see the main SQL tab). - The "Definition" input box should be displayed below the label at all times (unlike other controls), to maximise available space - The SQL tab view is broken for views (tested on PG 9.4), likely due to the TRUNCATE ACL issue: 2016-02-25 10:59:00,118: INFO werkzeug: 127.0.0.1 - - [25/Feb/2016 10:59:00] "GET /browser/view/sql/1/1/24587/27424/27555 HTTP/1.1" 500 - Traceback (most recent call last): File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/views.py", line 84, in view return self.dispatch_request(*args, **kwargs) File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/utils.py", line 248, in dispatch_request return method(*args, **kwargs) File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", line 242, in wrap return f(*args, **kwargs) File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", line 646, in sql result['datacl'] = self.parse_privileges(result['datacl']) File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", line 540, in parse_privileges db_privileges[privilege['privilege_type']]) KeyError: 'UNKNOWN' Thanks! -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EnterpriseDB UK: http://www.enterprisedb.com The Enterprise PostgreSQL Company ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-03-01 05:16 Surinder Kumar <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Surinder Kumar @ 2016-03-01 05:16 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: pgadmin-hackers Dave you forgot to attach patch. Can you please attach it ? On Thu, Feb 25, 2016 at 4:33 PM, Dave Page <[email protected]> wrote: > Hi > > On Wed, Feb 24, 2016 at 1:31 PM, Surinder Kumar < > [email protected]> wrote: > >> Hi, >> >> Please find the updated patch with following changes: >> >> 1. Integrated dependents and dependency tabs with view and rule node. >> 2. Added "Create Rule" option for Table node. >> 3. Added node_inode property for rule node as it has no child node. >> 4. Improved javascript code commenting and added pydoc in python files. >> 5. Name column under dependents tab showing wrong name for rule node >> due to typo mistake, It is fixed. >> >> Please review it and Let me know for any comments. >> > > Please find an updated patch attached, in which I tweaked some comments > and fixed a few minor issues as I reviewed the code. The following issues > need to be addressed please, then I'll take another look: > > - Don't show the definition SQL in the main properties tab. > > - Check Options is missing from the properties tab. > > - Views were made function in PostgreSQL v6.4! Why only load the module > for 9.1+? > > - The TRUNCATE ACL flag is not recognised, leading to ACLs being shown as: > arwdUNKNOWNxt > > - 4 character indents are not applied consistently to SQL templates > > - Why do servers pre-9.3 have VACUUM options in the SQL templates? > > - There should be no special casing of Slony schemas (backend_support.sql) > > - Don't include \'s on line ends of multiline comments or docstrings. > > - I can't create a Materialised View > > On the dialogs: > > - The "Definition" input box should have a border. > > - The "Definition" input box fill the vertical space assigned to it, > such that the border on the left runs top to bottom (see the main SQL tab). > > - The "Definition" input box should be displayed below the label at all > times (unlike other controls), to maximise available space > > - The SQL tab view is broken for views (tested on PG 9.4), likely due to > the TRUNCATE ACL issue: > > 2016-02-25 10:59:00,118: INFO werkzeug: 127.0.0.1 - - [25/Feb/2016 > 10:59:00] "GET /browser/view/sql/1/1/24587/27424/27555 HTTP/1.1" 500 - > Traceback (most recent call last): > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1836, in __call__ > return self.wsgi_app(environ, start_response) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1820, in wsgi_app > response = self.make_response(self.handle_exception(e)) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1403, in handle_exception > reraise(exc_type, exc_value, tb) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1817, in wsgi_app > response = self.full_dispatch_request() > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1477, in full_dispatch_request > rv = self.handle_user_exception(e) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1381, in handle_user_exception > reraise(exc_type, exc_value, tb) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1475, in full_dispatch_request > rv = self.dispatch_request() > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > line 1461, in dispatch_request > return self.view_functions[rule.endpoint](**req.view_args) > File > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/views.py", > line 84, in view > return self.dispatch_request(*args, **kwargs) > File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/utils.py", line 248, > in dispatch_request > return method(*args, **kwargs) > File > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > line 242, in wrap > return f(*args, **kwargs) > File > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > line 646, in sql > result['datacl'] = self.parse_privileges(result['datacl']) > File > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > line 540, in parse_privileges > db_privileges[privilege['privilege_type']]) > KeyError: 'UNKNOWN' > > > Thanks! > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company > ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-03-01 17:12 Dave Page <[email protected]> parent: Surinder Kumar <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Dave Page @ 2016-03-01 17:12 UTC (permalink / raw) To: Surinder Kumar <[email protected]>; +Cc: pgadmin-hackers Sorry - I believe this is it (I had to rescue it from the trash). On Tue, Mar 1, 2016 at 5:16 AM, Surinder Kumar <[email protected]> wrote: > Dave you forgot to attach patch. Can you please attach it ? > > On Thu, Feb 25, 2016 at 4:33 PM, Dave Page <[email protected]> wrote: >> >> Hi >> >> On Wed, Feb 24, 2016 at 1:31 PM, Surinder Kumar >> <[email protected]> wrote: >>> >>> Hi, >>> >>> Please find the updated patch with following changes: >>> >>> 1. Integrated dependents and dependency tabs with view and rule node. >>> 2. Added "Create Rule" option for Table node. >>> 3. Added node_inode property for rule node as it has no child node. >>> 4. Improved javascript code commenting and added pydoc in python files. >>> 5. Name column under dependents tab showing wrong name for rule node >>> due to typo mistake, It is fixed. >>> >>> Please review it and Let me know for any comments. >> >> >> Please find an updated patch attached, in which I tweaked some comments >> and fixed a few minor issues as I reviewed the code. The following issues >> need to be addressed please, then I'll take another look: >> >> - Don't show the definition SQL in the main properties tab. >> >> - Check Options is missing from the properties tab. >> >> - Views were made function in PostgreSQL v6.4! Why only load the module >> for 9.1+? >> >> - The TRUNCATE ACL flag is not recognised, leading to ACLs being shown as: >> arwdUNKNOWNxt >> >> - 4 character indents are not applied consistently to SQL templates >> >> - Why do servers pre-9.3 have VACUUM options in the SQL templates? >> >> - There should be no special casing of Slony schemas (backend_support.sql) >> >> - Don't include \'s on line ends of multiline comments or docstrings. >> >> - I can't create a Materialised View >> >> On the dialogs: >> >> - The "Definition" input box should have a border. >> >> - The "Definition" input box fill the vertical space assigned to it, >> such that the border on the left runs top to bottom (see the main SQL tab). >> >> - The "Definition" input box should be displayed below the label at all >> times (unlike other controls), to maximise available space >> >> - The SQL tab view is broken for views (tested on PG 9.4), likely due to >> the TRUNCATE ACL issue: >> >> 2016-02-25 10:59:00,118: INFO werkzeug: 127.0.0.1 - - [25/Feb/2016 >> 10:59:00] "GET /browser/view/sql/1/1/24587/27424/27555 HTTP/1.1" 500 - >> Traceback (most recent call last): >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1836, in __call__ >> return self.wsgi_app(environ, start_response) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1820, in wsgi_app >> response = self.make_response(self.handle_exception(e)) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1403, in handle_exception >> reraise(exc_type, exc_value, tb) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1817, in wsgi_app >> response = self.full_dispatch_request() >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1477, in full_dispatch_request >> rv = self.handle_user_exception(e) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1381, in handle_user_exception >> reraise(exc_type, exc_value, tb) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1475, in full_dispatch_request >> rv = self.dispatch_request() >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> line 1461, in dispatch_request >> return self.view_functions[rule.endpoint](**req.view_args) >> File >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/views.py", >> line 84, in view >> return self.dispatch_request(*args, **kwargs) >> File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/utils.py", line 248, >> in dispatch_request >> return method(*args, **kwargs) >> File >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> line 242, in wrap >> return f(*args, **kwargs) >> File >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> line 646, in sql >> result['datacl'] = self.parse_privileges(result['datacl']) >> File >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> line 540, in parse_privileges >> db_privileges[privilege['privilege_type']]) >> KeyError: 'UNKNOWN' >> >> >> Thanks! >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EnterpriseDB UK: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company > > -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EnterpriseDB UK: http://www.enterprisedb.com The Enterprise PostgreSQL Company -- Sent via pgadmin-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers Attachments: [application/octet-stream] view_and_role_node_v2-dave.patch (164.8K, 2-view_and_role_node_v2-dave.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py new file mode 100644 index 0000000..aa2605c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -0,0 +1,723 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements View Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases.schemas as schemas +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from .parse_definition import parse_definition + + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str, bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class ViewModule(CollectionNodeModule): + """ + class ViewModule(Object): + + A view collection Node which inherits CollectionNodeModule + class and define methods: + get_nodes - To generate collection node. + script_load - tells when to load js file. + csssnppets - add css to page + """ + NODE_TYPE = 'view' + COLLECTION_LABEL = gettext("Views") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(ViewModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that scid is catalog \ + # and schema object and catalog name is 'sys', 'dbo', + # 'information_schema' then only we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs \ + then allow us to decide whether we want to load this module or \ + not for that schema + """ + if super(ViewModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + # we will set template path for sql scripts + if server_type == 'ppas' and ver >= 90100: + template_path = 'templates/views/sql/edb_9.1_plus' + elif ver >= 90100: + template_path = 'templates/views/sql/9.4_plus' + else: + # Note: View is not supported below postgres version 9.1 + # Hence we will not load this module + return False + + SQL = render_template("/".join([ + template_path, 'backend_support.sql']), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', \ + # 'information_schema',then False (Do not load this module), \ + # othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for views, when any database node is + initialized, as views are under catalogs and schemas . + """ + return schemas.SchemaModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "templates/views/css/view.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + +# Set template_folder to views,as view also uses rules sql templates. +blueprint = ViewModule(__name__, template_folder="../views") + + +class ViewNode(PGChildNodeView): + """ + This is a class for view node which inherits the + properties and methods from PGChildNodeView class and define + various methods to list, create, update and delete view. + + Variables: + --------- + * node_type - tells which type of node it is + * parent_ids - id with its type and name of parent nodes + * ids - id with type and name of extension module being used. + * operations - function routes mappings defined. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'vid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines whether Javascript exists for this node. + """ + return make_response( + render_template( + "templates/views/js/views.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will check the + database connection before running the view. It will also attach + manager, conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[ + kwargs['did']]['datlastsysoid'] + ver = self.manager.version + + # we will set template path for sql scripts + if ver >= 90400: + self.template_path = 'templates/views/sql/9.4_plus' + elif ver >= 90300: + self.template_path = 'templates/views/sql/9.3_plus' + elif ver >= 90200: + self.template_path = 'templates/views/sql/9.2_plus' + elif ver >= 90100: + self.template_path = 'templates/views/sql/9.1_plus' + else: + self.template_path = 'templates/views/sql/pre_9.1' + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid): + """ + Fetches all views properties and render into properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), scid=scid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + Lists all views under the Views Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + scid, + row['name'], + icon="icon-view" + )) + + return make_json_response( + data=res, + status=200 + ) + + def parse_views_privileges(self, db_privileges): + """ + This function forms a separate list of grantable + and non grantable privileges. + """ + acl = {'grantor': db_privileges['grantor'], + 'grantee': db_privileges['grantee'], + 'privileges': [] + } + + privileges = [] + for idx, priv in enumerate(db_privileges['privileges']): + if db_privileges['grantable'][idx]: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": True + }) + else: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": False + }) + + acl['privileges'] = privileges + + return acl + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + Fetches the properties of an individual view + and render in the properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join( + [self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + return ajax_response( + response=result, + status=200 + ) + + @staticmethod + def formatter(result): + """ This function formats output for security label & variables""" + frmtd_result = dict() + sec_lbls = [] + if 'seclabels' in result and result['seclabels'] is not None: + for sec in result['seclabels']: + import re + sec = re.search(r'([^=]+)=(.*$)', sec) + sec_lbls.append({ + 'provider': sec.group(1), + 'security_label': sec.group(2) + }) + + frmtd_result.update({"seclabels": sec_lbls}) + return frmtd_result + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will create a new view object + """ + required_args = [ + 'name', + 'definition' + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + SQL = render_template("/".join( + [self.template_path, 'view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=view_id) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid): + """ + This function will update a view object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid) + try: + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View updated"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info=gettext("Nothing to update"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid): + """ + This function will drop a view object + """ + + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + + # Get name for view from did + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), vid=vid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + + # drop view + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), + nspname=res_data['rows'][0]['nspname'], + name=res_data['rows'][0]['name'], cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid=None): + """ + This function returns modified SQL + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + SQL = self.getSQL(gid, sid, data, vid) + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("-- Nothing changed"), + status=200 + ) + + @staticmethod + def parse_privileges(str_privileges, object_type='VIEW'): + """Parse Privileges.""" + db_privileges = { + 'a': 'INSERT', + 'r': 'SELECT', + 'w': 'UPDATE', + 'd': 'DELETE', + 'x': 'REFERENCE', + 't': 'TRIGGER' + } + privileges = [] + for priv in str_privileges: + priv_with_grant = [] + priv_without_grant = [] + for privilege in priv['privileges']: + if privilege['with_grant']: + priv_with_grant.append( + db_privileges[privilege['privilege_type']]) + elif privilege['privilege']: + priv_without_grant.append( + db_privileges[privilege['privilege_type']]) + priv_with_grant = ", ".join(priv_with_grant) + priv_without_grant = ", ".join(priv_without_grant) + + # Server Level validation + if 'grantee' in priv: + privileges.append( + { + 'grantee': priv['grantee'] if 'grantee' in priv else '', + 'with_grant': priv_with_grant, + 'without_grant': priv_without_grant + } + ) + else: + return '' + return privileges + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will genrate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + SQL = render_template("/".join( + [self.template_path, 'update.sql']), data=data, + o_data=old_data, conn=self.conn) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def sql(self, gid, sid, did, scid, vid): + """ + This function will generate sql to render into the sql panel + """ + SQL_data = '' + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch all privileges for view + SQL = render_template("/".join( + [self.template_path, 'acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0]['datacl'].append(priv) + else: + res['rows'][0]['datacl'] = [priv] + + result = res['rows'][0] + if 'datacl' in result: + result['datacl'] = self.parse_privileges(result['datacl']) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), + data=result, + conn=self.conn, + display_comments=True + ) + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'grant.sql']), data=result) + + SQL_data += SQL + + """Get all non system rules of view node, generate their sql and render + into sql tab""" + self.temp_path = 'rules/templates/rules/sql' + SQL = render_template("/".join( + [self.temp_path, 'properties.sql']), vid=vid) + status, data_prop = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for rule in data_prop['rows']: + + # Generate SQL only for non system rule + if rule['name'] != '_RETURN': + res = [] + SQL = render_template("/".join( + [self.temp_path, 'properties.sql']), + rid=rule['oid'] + ) + status, res = self.conn.execute_dict(SQL) + res_data = parse_definition(res) + SQL = render_template("/".join( + [self.temp_path, 'create.sql']), + data=res_data, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + + return ajax_response(response=SQL_data) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid): + """ + This function gets the dependents and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependents_result = self.get_dependents(self.conn, vid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid): + """ + This function gets the dependencies and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependencies_result = self.get_dependencies(self.conn, vid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +ViewNode.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py new file mode 100644 index 0000000..2abb0af --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/parse_definition.py @@ -0,0 +1,58 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Parse definition field data """ + +from pgadmin.utils.ajax import internal_server_error + + +def parse_definition(res): + """ + This function extracts: + - events + - do_instead + - statements + - condition + from the defintion row, forms an array with fields and returns it. + """ + res_data = [] + try: + res_data = res['rows'][0] + data_def = res_data['definition'] + import re + # Parse data for event + e_match = re.search(r"ON\s+(.*)\s+TO", data_def) + event_data = e_match.group(1) if e_match is not None else None + event = event_data if event_data is not None else '' + + # Parse data for do instead + inst_match = re.search(r"\s+(INSTEAD)\s+", data_def) + instead_data = inst_match.group(1) if inst_match is not None else None + instead = True if instead_data is not None else False + + # Parse data for condition + condition_match = re.search(r"(?:WHERE)\s+(.*)\s+(?:DO)", data_def) + condition_data = condition_match.group(1) \ + if condition_match is not None else None + condition = condition_data if condition_data is not None else '' + + # Parse data for statements + statement_match = re.search( + r"(?:DO\s+)(?:INSTEAD\s+)?((.|\n)*)", data_def) + statement_data = statement_match.group(1) if statement_match else None + statement = statement_data if statement_data is not None else '' + + # set columns parse data + res_data['event'] = event.lower().capitalize() + res_data['do_instead'] = instead + res_data['statements'] = statement + res_data['condition'] = condition + except Exception as e: + return internal_server_error(errormsg=str(e)) + return res_data diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py new file mode 100644 index 0000000..d37f471 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/__init__.py @@ -0,0 +1,522 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Rule Node """ + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases.schemas.views as views +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from ..parse_definition import parse_definition + + +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str, bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + + +class RuleModule(CollectionNodeModule): + """ + class RuleModule(Object): + + A rule collection Node which inherits CollectionNodeModule + class and define methods: + get_nodes - To generate collection node. + script_load - tells when to load js file. + csssnppets - add css to page + """ + NODE_TYPE = 'rule' + COLLECTION_LABEL = gettext("Rules") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(RuleModule, self).__init__(*args, **kwargs) + + # Before loading this module we need to make sure that if scid is a catalog + # and catalog name is not 'sys', 'dbo' or 'information_schema' only then + # do we load this module + def BackendSupported(self, manager, **kwargs): + """ + This function will validate schema name & scid against catalogs + then allow us to decide if we want to load this module or + not for that schema + """ + if super(RuleModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection() + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = manager.version + server_type = manager.server_type + template_path = 'rules/sql' + SQL = render_template("/".join( + [template_path, 'backend_support.sql'] + ), scid=kwargs['scid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check scid is catalog and from 'sys', 'dbo', \ + # 'information_schema', then False (Do not load this module), \ + # othewise True + if res is True: + return False + else: + return True + + def get_nodes(self, gid, sid, did, scid, vid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(vid) + + @property + def node_inode(self): + """ + If a node has children return True otherwise False + """ + return False + + @property + def script_load(self): + """ + Load the module script for rule, when any of the database nodes are + initialized. + """ + return views.ViewModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "rules/css/rule.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +# Create blueprint of RuleModule. +blueprint = RuleModule(__name__) + + +class RuleView(PGChildNodeView): + """ + This is a class for rule node which inherits the + properties and methods from PGChildNodeView class and define + various methods to list, create, update and delete rule. + + Variables: + --------- + * node_type - tells which type of node it is + * parent_ids - id with its type and name of parent nodes + * ids - id with type and name of extension module being used. + * operations - function routes mappings defined. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'vid'} + ] + ids = [ + {'type': 'int', 'id': 'rid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines whether Javascript exists for this node. + """ + return make_response( + render_template( + "rules/js/rules.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will check the + database connection before running a view. It will also attach + manager, conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + ver = self.manager.version + self.template_path = 'rules/sql' + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, vid): + """ + Fetch all rule properties and render into properties tab + """ + + # fetch schema name by schema id + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), vid=vid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, vid): + """ + List all the rules under the Rules Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), vid=vid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + vid, + row['name'], + icon="icon-rule" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, vid, rid): + """ + Fetch the properties of an individual rule and render in properties tab + + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql'] + ), rid=rid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return ajax_response( + response=parse_definition(res), + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, vid): + """ + This function will create a new rule object + """ + required_args = [ + 'name', + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + """ Fetch the rule id against rule name to display node in tree browser""" + SQL = render_template("/".join( + [self.template_path, 'rule_id.sql']), rule_name=data['name']) + status, rule_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=rule_id) + return jsonify( + node=self.blueprint.generate_browser_node( + rule_id, + vid, + data['name'], + icon="icon-rule" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid, rid): + """ + This function will update a rule object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid, rid) + try: + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule updated"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info=gettext("Nothing to update"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid, rid): + """ + This function will drop a rule object + """ + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + # Get name for rule from did + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), rid=rid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + # drop rule + rset = res_data['rows'][0] + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), + rulename=rset['rulename'], + relname=rset['relname'], + nspname=rset['nspname'], + cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid, rid=None): + """ + This function returns modified SQL + """ + data = request.args + SQL = self.getSQL(gid, sid, data, vid, rid) + if SQL and isinstance(SQL, basestring) and \ + SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("--modified SQL"), + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, vid, rid): + """ + This function will generate sql to render into the sql panel + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + res_data = parse_definition(res) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), + data=res_data, display_comments=True) + + return ajax_response(response=SQL) + + def getSQL(self, gid, sid, data, vid, rid): + """ + This function will genrate sql from model data + """ + try: + if rid is not None: + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + res_data = [] + res_data = parse_definition(res) + if not status: + return internal_server_error(errormsg=res) + old_data = res_data + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data + ) + else: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + return SQL + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid, rid): + """ + This function gets the dependents and returns an ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + rid: Rule ID + """ + dependents_result = self.get_dependents(self.conn, rid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid, rid): + """ + This function gets the dependencies and returns sn ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + rid: Rule ID + """ + dependencies_result = self.get_dependencies(self.conn, rid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +RuleView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png new file mode 100644 index 0000000..cfe6ed2 Binary files /dev/null and b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/coll-rule.png differ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png new file mode 100644 index 0000000..8b49780 Binary files /dev/null and b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/static/img/rule.png differ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css new file mode 100644 index 0000000..d2b036a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/css/rule.css @@ -0,0 +1,8 @@ +.icon-rule{ + background-image: url('{{ url_for('NODE-rule.static', filename='img/rule.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js new file mode 100644 index 0000000..652c80d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/js/rules.js @@ -0,0 +1,252 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + + /** + Create and add a rule collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-rule']) { + var rules = pgAdmin.Browser.Nodes['coll-rule'] = + pgAdmin.Browser.Collection.extend({ + node: 'rule', + label: '{{ _('Rules') }}', + type: 'coll-rule', + columns: ['name', 'owner', 'comment'] + }); + }; + + + /** + Create and Add an View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['rule']) { + pgAdmin.Browser.Nodes['rule'] = pgAdmin.Browser.Node.extend({ + parent_type: ['view'], + type: 'rule', + label: '{{ _('rule') }}', + collection_type: 'coll-rule', + hasSQL: true, + hasDepends: true, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create rule" menu option into context and object menu + for the following nodes: + coll-rule, rule and view and table. + @property {data} - Allow create rule option on schema node or + system rules node. + */ + pgBrowser.add_menus([{ + name: 'create_rule_on_coll', node: 'coll-rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'rule', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_rule', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Rule...') }}', + icon: 'wcTabIcon icon-rule', data: {action: 'create', check: true}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the rule node and specify the node + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', + type: 'text', disabled: function(m){ + return !m.isNew(); + } + }, + { + id: 'oid', label:'{{ _('OID') }}', + type: 'text', disabled: true + }, + { + id: 'schema', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + + // It is used while generating sql + m.set('schema', m.node_info.schema.label); + } + }, + { + id: 'view', label:'{{ _('') }}', + type: 'text', visible: false, disabled: function(m){ + + // It is used while generating sql + m.set('view', m.node_info.view.label); + } + }, + { + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', type: 'multiline' + }, + { + id: 'event', label:'{{ _('Event') }}', control: 'select2', + group: 'Definition', type: 'text', + select2: { + width: '100%' + }, + options:[ + {label: 'Select', value: 'Select'}, + {label: 'Insert', value: 'Insert'}, + {label: 'Update', value: 'Update'}, + {label: 'Delete', value: 'Delete'} + ] + }, + { + id: 'do_instead', label:'{{ _('Do Instead') }}', group: 'Definition', + type: 'switch', 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'condition', label:'{{ _('Condition') }}', + type: 'text', group: 'Definition', + control: 'sql-field' + }, + { + id: 'statements', label:'{{ _('Definition') }}', + type: 'text', group: 'Statements', control: 'sql-field' + }, + { + id: 'system_rule', label:'{{ _('System rule?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'enabled', label:'{{ _('Enabled?') }}', + type: 'switch', mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + } + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') + { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + } + else + { + this.errorModel.unset('name'); + } + return null; + } + }), + + // Show or hide create rule menu option on parent node + canCreate: function(itemData, item, data) { + + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create rule + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-rule' == d._type) { + + //Check if we are not child of rule + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + prev_k = t.hasParent(prev_j) ? t.parent(prev_j) : null; + prev_f = prev_k ? t.itemData(prev_k) : null; + if( prev_f._type == 'catalog') { + return false; + } else { + return true; + } + } + + /** + Check if it is view and its parent node is schema + then allow to create Rule + */ + else if('view' == d._type){ + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + if(prev_e._type == 'schema') { + return true; + }else{ + return false; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // By default we do not want to allow create menu + return true; + + } + + }); + } + + return pgBrowser.Nodes['coll-rule']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql new file mode 100644 index 0000000..3c7436f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/create.sql @@ -0,0 +1,26 @@ +{#============Create Rule=============#} +{% if display_comments %} +-- Rule: {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP Rule {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.view %} +CREATE OR REPLACE RULE {{ conn|qtIdent(data.name) }} AS + ON {{ data.event|upper if data.event else 'SELECT' }} TO {{ conn|qtIdent(data.schema, data.view) }} +{% if data.condition %} + WHERE {{ data.condition }} +{% endif %} + DO{% if data.is_instead == True %} +{{ ' INSTEAD' }} + {% else %} +{{ ' ' }} + {% endif %} +{% if data.statements %} +{{ data.statements.rstrip(';') }}; +{% else %} +NOTHING; +{% endif %} +{% if data.comment %} +COMMENT ON RULE {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql new file mode 100644 index 0000000..7b8318b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/delete.sql @@ -0,0 +1,16 @@ +{#======== Drop/Cascade Rule =========#} +{% if rid %} +SELECT + rw.rulename, cl.relname, nsp.nspname +FROM + pg_rewrite rw +JOIN + pg_class cl ON cl.oid=rw.ev_class +JOIN + pg_namespace nsp ON nsp.oid=cl.relnamespace +WHERE + rw.oid={{ rid }}; +{% endif %} +{% if rulename and relname and nspname %} +DROP RULE {{ conn|qtIdent(rulename) }} ON {{ conn|qtIdent(nspname, relname) }} {% if cascade %} CASCADE {% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql new file mode 100644 index 0000000..eda1025 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/properties.sql @@ -0,0 +1,27 @@ +{#===================Fetch Rules====================#} +{% if vid or rid %} +SELECT + rw.oid AS oid, rw.rulename AS name, rw.*, relname AS view, + CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname AS schema, description AS comment, + {#==Checks whether it is system rule or not===#} + CASE WHEN rw.rulename = '_RETURN' THEN True ELSE False END AS system_rule, + CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled, + pg_get_ruledef(rw.oid, true) AS definition +FROM + pg_rewrite rw +JOIN + pg_class cl ON cl.oid=rw.ev_class +JOIN + pg_namespace nsp ON nsp.oid=cl.relnamespace +LEFT OUTER JOIN + pg_description des ON (des.objoid=rw.oid AND des.classoid='pg_rewrite'::regclass) +WHERE + {% if vid %} + ev_class = {{ vid }} + {% elif rid %} + rw.oid = {{ rid }} + {% endif %} +ORDER BY + rw.rulename + {% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql new file mode 100644 index 0000000..3dc6dba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/rule_id.sql @@ -0,0 +1,9 @@ +{#========Below will provide rule id for last created rule========#} +{% if rule_name %} +SELECT + rw.oid +FROM + pg_rewrite rw +WHERE + rw.rulename={{ rule_name|qtLiteral }} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql new file mode 100644 index 0000000..b50369a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/rules/templates/rules/sql/update.sql @@ -0,0 +1,22 @@ +{#============Update Rule=============#} +{% if data.event or data.do_instead or data.condition %} +CREATE OR REPLACE RULE {{ conn|qtIdent(o_data.name) }} AS + ON {% if data.event and data.event != o_data.event %}{{ data.event|upper }}{% else %}{{ o_data.event|upper }}{% endif %} + TO {{ conn|qtIdent(o_data.schema, o_data.view) }} +{% if data.condition and o_data.condition != data.condition %} + WHERE {{ data.condition }} +{% elif data.condition is not defined and o_data.condition %} + WHERE {{ o_data.condition }} +{% endif %} + DO{% if data.do_instead in ['true', True] %}{{ ' INSTEAD' }}{% else %}{{ ' ' }} +{% endif %} +{% if data.statements and data.statements != o_data.statements %} + {{ data.statements.rstrip(';') }}; + {% elif data.statements is not defined and o_data.statements %} + {{ o_data.statements.rstrip(';') }}; +{% else %} + NOTHING; +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON RULE {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.schema, o_data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png new file mode 100644 index 0000000..078d533 Binary files /dev/null and b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png differ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png new file mode 100644 index 0000000..44116db Binary files /dev/null and b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png differ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css new file mode 100644 index 0000000..88a2f43 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/css/view.css @@ -0,0 +1,8 @@ +.icon-view{ + background-image: url('{{ url_for('NODE-view.static', filename='img/view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js new file mode 100644 index 0000000..d3cea78 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/js/views.js @@ -0,0 +1,310 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + // Define Security Model with fields and validation + var SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + securitylabel: undefined + }, + schema: [{ + id: 'provider', label: '{{ _('Provider') }}', + type: 'text', editable: true + },{ + id: 'security_label', label: '{{ _('Security Label') }}', + type: 'text', editable: true + }], + + validate: function() { + var err = {}, + errmsg = null, + field_name = this.get('provider'), + field_security_label = this.get('security_label'); + + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['provider'] = '{{ _("Provider cannot be empty.") }}'; + errmsg = errmsg || err['provider']; + this.errorModel.set('provider', errmsg); + return errmsg; + }else{ + this.errorModel.unset('provider'); + } + + if (_.isUndefined(field_security_label) || _.isNull(field_security_label) || + String(field_security_label).replace(/^\s+|\s+$/g, '') == '') { + err['security_label'] = '{{ _("Security Label cannot be empty.") }}'; + errmsg = errmsg || err['security_label']; + this.errorModel.set('security_label', errmsg); + return errmsg; + }else{ + this.errorModel.unset('security_label'); + } + return null; + } + }); + + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-view']) { + var views= pgAdmin.Browser.Nodes['coll-view'] = + pgAdmin.Browser.Collection.extend({ + node: 'view', + label: '{{ _('Views') }}', + type: 'coll-view', + columns: ['name', 'owner'] + }); + }; + + /** + Create and Add a View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['view']) { + pgAdmin.Browser.Nodes['view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'view', + label: '{{ _('View') }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_view_on_coll', node: 'coll-view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text' + }, + { + id: 'oid', label:'{{ _('Oid') }}', cell: 'string', + type: 'text', disabled: true + }, + { + id: 'owner', label:'{{ _('Owner') }}', cell: 'string', control: 'node-list-by-name', + node: 'role' + }, + { + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline' + }, + { + id: 'system_view', label:'{{ _('System view?') }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'], 'options': { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'security_barrier', label:'{{ _('Security Barrier') }}', cell: 'string', + type: 'switch', min_version: '90200', + group: 'Definition', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + { + id: 'check_option', label:'{{ _('Check Options') }}', + control: 'select2', group: 'Definition', type: 'text', + min_version: '90400', mode:['create', 'edit'], + select2: { + + // set select2 option width to 100% + width: '100%' + }, + options:[ + {label: "No", value: "no"}, + {label: "Local", value: "local"}, + {label: "Cascaded", value: "cascaded"} + ] + }, + { + id: 'definition', label:'{{ _('Definition') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], group: 'Definition', + control: 'sql-field' + }, + { + id: 'm_view', label:'{{ _('Materialized view?') }}', cell: 'string', + type: 'switch', mode: ['properties'], min_version: '90300', + group: 'Materialization', options: { + 'onText': 'Yes', 'offText': 'No', 'onColor': 'success', + 'offColor': 'default', 'size': 'small' + } + }, + + // Add Privilege Control + { + id: 'datacl', label: '{{ _('Privileges') }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _('Security') }}', + mode: ['properties', 'edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _('Security Labels') }}', + model: SecurityModel, editable: false, type: 'collection', + canEdit: false, group: '{{ _('Security') }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + } + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + } + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql new file mode 100644 index 0000000..807fb04 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{vid}}) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql new file mode 100644 index 0000000..4e290a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql new file mode 100644 index 0000000..9487458 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/properties.sql @@ -0,0 +1,29 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql new file mode 100644 index 0000000..2a3fbca --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql new file mode 100644 index 0000000..7d19b68 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXIST + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql new file mode 100644 index 0000000..a345e15 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql new file mode 100644 index 0000000..4e290a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql new file mode 100644 index 0000000..3da7801 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/properties.sql @@ -0,0 +1,39 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, spc.spcname, + pg_get_userbyid(c.relowner) AS owner, c.relacl, description As comment, + pg_get_viewdef(c.oid, true) AS definition, nsp.nspname AS schema, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.2_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql new file mode 100644 index 0000000..b60bfa8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/acl.sql @@ -0,0 +1,30 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql new file mode 100644 index 0000000..ab39cac --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql new file mode 100644 index 0000000..a345e15 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql new file mode 100644 index 0000000..044ff71 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/delete.sql @@ -0,0 +1,12 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname AS name, nsp.nspname +FROM + pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql new file mode 100644 index 0000000..f4e3498 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/properties.sql @@ -0,0 +1,60 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, c.relkind, nsp.nspname AS schema, + c.relispopulated AS ispopulated, spc.spcname, pg_get_userbyid(c.relowner) + AS owner, c.relacl, description, pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, + (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql new file mode 100644 index 0000000..d258632 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update view=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.3_plus/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql new file mode 100644 index 0000000..f0c4c01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/acl.sql @@ -0,0 +1,35 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl FROM pg_class cl + LEFT OUTER JOIN + pg_shdescription descr + ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql new file mode 100644 index 0000000..53a9bdb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/delete.sql @@ -0,0 +1,13 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT + c.relname As name, nsp.nspname +FROM + pg_class c +LEFT JOIN + pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql new file mode 100644 index 0000000..fa4dcf6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/properties.sql @@ -0,0 +1,65 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/9.4_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql new file mode 100644 index 0000000..b5d2225 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/acl.sql @@ -0,0 +1,33 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql new file mode 100644 index 0000000..5c0648d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql new file mode 100644 index 0000000..f926027 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/properties.sql @@ -0,0 +1,63 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT nsp.nspname + FROM pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/edb_9.1_plus/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql new file mode 100644 index 0000000..46c5e5f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/acl.sql @@ -0,0 +1,32 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT relacl FROM pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM (SELECT aclexplode(relacl) as d FROM pg_class cl1 WHERE cl1.oid = {{ vid }}::OID) a) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql new file mode 100644 index 0000000..e3f8f4f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/backend_support.sql @@ -0,0 +1,20 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM + pg_namespace nsp +WHERE + nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql new file mode 100644 index 0000000..2ac1e3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier else '' }}{% if data.security_barrier %} +security_barrier={{ data.security_barrier }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql new file mode 100644 index 0000000..8fc4e01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/delete.sql @@ -0,0 +1,9 @@ +{#============================Drop/Cascade view by name=========================#} +{% if vid %} +SELECT c.relname As name, nsp.nspname + FROM pg_class c + LEFT JOIN pg_namespace nsp on c.relnamespace = nsp.oid + WHERE c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql new file mode 100644 index 0000000..9fa7181 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/grant.sql @@ -0,0 +1,14 @@ +{#=====Grant Permissions to User Role on Views/Tables====#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql new file mode 100644 index 0000000..61c4a0e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/properties.sql @@ -0,0 +1,65 @@ +{#============================Fetch View Properties=========================#} +{% if (vid and datlastsysoid) or scid %} +SELECT c.oid, c.xmin, c.relname AS name, c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, spc.spcname, nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + {#=============Checks if it is materialized view========#} + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels + , substring(array_to_string(c.reloptions, ',') FROM 'check_option=([a-z]*)') AS check_option + , substring(array_to_string(c.reloptions, ',') FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier + , substring(array_to_string(c.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age + , substring(array_to_string(c.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age + , substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age + , c.reloptions AS reloptions, tst.reloptions AS toast_reloptions + , (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT r.rulename FROM pg_rewrite r + WHERE ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) OR (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT pr.rolname FROM pg_roles pr WHERE pr.rolcanlogin ORDER BY pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql new file mode 100644 index 0000000..1867038 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/update.sql @@ -0,0 +1,81 @@ +{#============================Update View=========================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ data.schema }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.APPLY(conn, 'TABLE', priv.grantee, data.name, data.schema, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql new file mode 100644 index 0000000..65648c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/sql/pre_9.1/view_id.sql @@ -0,0 +1,4 @@ +{#==Below will provide view id for last created view==#} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-03-23 15:44 Surinder Kumar <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Surinder Kumar @ 2016-03-23 15:44 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: pgadmin-hackers This patch contains following nodes: 1. View Node 2. Materialized View Node This patch depends on couple of patches: 1. Control for displaying "auto vacuum" fields into grid 2. Tables sub node patch. First apply these two patches then apply patch this patch. Please review the patch. On Tue, Mar 1, 2016 at 10:42 PM, Dave Page <[email protected]> wrote: > Sorry - I believe this is it (I had to rescue it from the trash). > > On Tue, Mar 1, 2016 at 5:16 AM, Surinder Kumar > <[email protected]> wrote: > > Dave you forgot to attach patch. Can you please attach it ? > > > > On Thu, Feb 25, 2016 at 4:33 PM, Dave Page <[email protected]> wrote: > >> > >> Hi > >> > >> On Wed, Feb 24, 2016 at 1:31 PM, Surinder Kumar > >> <[email protected]> wrote: > >>> > >>> Hi, > >>> > >>> Please find the updated patch with following changes: > >>> > >>> 1. Integrated dependents and dependency tabs with view and rule node. > >>> 2. Added "Create Rule" option for Table node. > >>> 3. Added node_inode property for rule node as it has no child node. > >>> 4. Improved javascript code commenting and added pydoc in python files. > >>> 5. Name column under dependents tab showing wrong name for rule node > >>> due to typo mistake, It is fixed. > >>> > >>> Please review it and Let me know for any comments. > >> > >> > >> Please find an updated patch attached, in which I tweaked some comments > >> and fixed a few minor issues as I reviewed the code. The following > issues > >> need to be addressed please, then I'll take another look: > >> > >> - Don't show the definition SQL in the main properties tab. > Done > >> > >> - Check Options is missing from the properties tab. > Added > >> > >> - Views were made function in PostgreSQL v6.4! Why only load the module > >> for 9.1+? > Fixed > >> > >> - The TRUNCATE ACL flag is not recognised, leading to ACLs being shown > as: > >> arwdUNKNOWNxt > Fixed > >> > >> - 4 character indents are not applied consistently to SQL templates > Done > >> > >> - Why do servers pre-9.3 have VACUUM options in the SQL templates? > Done > >> > >> - There should be no special casing of Slony schemas > (backend_support.sql) > Done > >> > >> - Don't include \'s on line ends of multiline comments or docstrings. > Done > >> > >> - I can't create a Materialised View > This patch contains materialized view now > >> > >> On the dialogs: > >> > >> - The "Definition" input box should have a border. > Fixed > >> > >> - The "Definition" input box fill the vertical space assigned to it, > >> such that the border on the left runs top to bottom (see the main SQL > tab). > Fixed > >> > >> - The "Definition" input box should be displayed below the label at > all > >> times (unlike other controls), to maximise available space > Fixed > >> > >> - The SQL tab view is broken for views (tested on PG 9.4), likely due to > >> the TRUNCATE ACL issue: > >> > >> 2016-02-25 10:59:00,118: INFO werkzeug: 127.0.0.1 - - [25/Feb/2016 > >> 10:59:00] "GET /browser/view/sql/1/1/24587/27424/27555 HTTP/1.1" 500 - > >> Traceback (most recent call last): > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1836, in __call__ > >> return self.wsgi_app(environ, start_response) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1820, in wsgi_app > >> response = self.make_response(self.handle_exception(e)) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1403, in handle_exception > >> reraise(exc_type, exc_value, tb) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1817, in wsgi_app > >> response = self.full_dispatch_request() > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1477, in full_dispatch_request > >> rv = self.handle_user_exception(e) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1381, in handle_user_exception > >> reraise(exc_type, exc_value, tb) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1475, in full_dispatch_request > >> rv = self.dispatch_request() > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", > >> line 1461, in dispatch_request > >> return self.view_functions[rule.endpoint](**req.view_args) > >> File > >> > "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/views.py", > >> line 84, in view > >> return self.dispatch_request(*args, **kwargs) > >> File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/utils.py", line > 248, > >> in dispatch_request > >> return method(*args, **kwargs) > >> File > >> > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > >> line 242, in wrap > >> return f(*args, **kwargs) > >> File > >> > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > >> line 646, in sql > >> result['datacl'] = self.parse_privileges(result['datacl']) > >> File > >> > "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", > >> line 540, in parse_privileges > >> db_privileges[privilege['privilege_type']]) > >> KeyError: 'UNKNOWN' > >> > Done > >> > >> Thanks! > >> > >> -- > >> Dave Page > >> Blog: http://pgsnake.blogspot.com > >> Twitter: @pgsnake > >> > >> EnterpriseDB UK: http://www.enterprisedb.com > >> The Enterprise PostgreSQL Company > > > > > > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company > -- Sent via pgadmin-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers Attachments: [application/octet-stream] view_material_view_node_patch_v3.patch (177.9K, 3-view_material_view_node_patch_v3.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py new file mode 100644 index 0000000..0ebf5c3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -0,0 +1,1327 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements View and Materialized View Node""" + +import json +from flask import render_template, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error, \ + bad_request +from pgadmin.browser.utils import PGChildNodeView +import pgadmin.browser.server_groups.servers as servers +from pgadmin.browser.server_groups.servers.databases.schemas.utils import \ + SchemaChildModule, parse_rule_definition +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps + + +""" + This module is responsible for generating two nodes + 1) View + 2) Materialized View + + We have created single file because view & material view has same + functionality. It allows us to share the same submodules for both + view, and material view modules. + + This modules uses separate template paths for each respective node + - templates/view for View node + - templates/materialized_view for MaterializedView node + + [Each path contains node specific js files as well as sql template files.] +""" + + +class ViewModule(SchemaChildModule): + """ + class ViewModule(SchemaChildModule): + + A module class for View node derived from SchemaChildModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the View and it's base module. + + * get_nodes(gid, sid, did, scid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for View, when any of the server node is + initialized. + """ + NODE_TYPE = 'view' + COLLECTION_LABEL = gettext("Views") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ViewModule and it's base module. + + Args: + *args: + **kwargs: + """ + super(ViewModule, self).__init__(*args, **kwargs) + self.min_ver = None + self.max_ver = None + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for views, when any database node is + initialized, as views are under schemas. + """ + return servers.ServerModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "view/css/view.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "materialized_view/css/materialized_view.css", + node_type='materialized_view', + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +class MaterializedViewModule(ViewModule): + """ + class MaterializedViewModule(ViewModule) + A module class for the materialized view and view node derived + from ViewModule. + """ + + NODE_TYPE = 'materialized_view' + COLLECTION_LABEL = gettext("Materialized Views") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the MaterializedViewModule and + it's base module. + + Args: + *args: + **kwargs: + """ + super(MaterializedViewModule, self).__init__(*args, **kwargs) + self.min_ver = 90300 + self.max_ver = None + +view_blueprint = ViewModule(__name__) +materialized_view_blueprint = MaterializedViewModule( + __name__) + + +def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to instance of the method. + + Assumptions: + This function will always be used as decorator of a class method. + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext("Connection to the server has been lost!") + ) + self.datlastsysoid = self.manager.db_info[ + kwargs['did']]['datlastsysoid'] + + # Set template path for sql scripts + self.template_path = self.template_initial + '/' + ( + self.ppas_template_path(self.manager.version) + if self.manager.server_type == 'ppas' else + self.pg_template_path(self.manager.version) + ) + + return f(*args, **kwargs) + + return wrap + + +class ViewNode(PGChildNodeView): + """ + This class is responsible for generating routes for view node. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the ViewNode and it's base view. + + * module_js() + - Request handler for module.js routes for the view node module + javascript, which returns javscript for this module. + + * list() + - This function is used to list all the view nodes within the + collection. + + * nodes() + - This function will used to create all the child node within the + collection, Here it will create all the view node. + + * properties(gid, sid, did, scid) + - This function will show the properties of the selected view node. + + * create(gid, sid, did, scid) + - This function will create the new view object. + + * update(gid, sid, did, scid) + - This function will update the data for the selected view node. + + * delete(self, gid, sid, scid): + - This function will drop the view object + + * msql(gid, sid, did, scid) + - This function is used to return modified SQL for the selected view + node. + + * get_sql(data, scid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid): + - This function will generate sql to show it in sql pane for the view + node. + + * dependency(gid, sid, did, scid): + - This function will generate dependency list show it in dependency + pane for the selected view node. + + * dependent(gid, sid, did, scid): + - This function will generate dependent list to show it in dependent + pane for the selected view node. + """ + node_type = view_blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'vid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}], + 'get_tblspc': [{'get': 'get_tblspc'}, {'get': 'get_tblspc'}], + 'get_vacuum_defaults': [ + {'get': 'get_vacuum_defaults'}, + {'get': 'get_vacuum_defaults'}], + 'get_vacuum_toast_defaults': [ + {'get': 'get_vacuum_toast_defaults'}, + {'get': 'get_vacuum_toast_defaults'}] + }) + + def __init__(self, *args, **kwargs): + """ + Initialize the variables used by methods of ViewNode. + """ + + super(ViewNode, self).__init__(*args, **kwargs) + + self.manager = None + self.conn = None + self.template_path = None + self.template_initial = 'view' + + @staticmethod + def ppas_template_path(ver): + """ + Returns the template path for PPAS servers. + """ + return 'ppas/9.3_plus' + + @staticmethod + def pg_template_path(ver): + """ + Returns the template path for PostgreSQL servers. + """ + if ver >= 90400: + return 'pg/9.4_plus' + elif ver >= 90300: + return 'pg/9.3_plus' + elif ver >= 90200: + return 'pg/9.2_plus' + elif ver >= 90100: + return 'pg/9.1_plus' + return 'pg/9.1_plus' + + @check_precondition + def list(self, gid, sid, did, scid): + """ + Fetches all views properties and render into properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), scid=scid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + Lists all views under the Views Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + scid, + row['name'], + icon="icon-view" + )) + + return make_json_response( + data=res, + status=200 + ) + + def parse_views_privileges(self, db_privileges): + """ + This function forms a separate list of grantable + and non grantable privileges. + """ + acl = {'grantor': db_privileges['grantor'], + 'grantee': db_privileges['grantee'], + 'privileges': [] + } + + privileges = [] + for idx, priv in enumerate(db_privileges['privileges']): + if db_privileges['grantable'][idx]: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": True + }) + else: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": False + }) + + acl['privileges'] = privileges + + return acl + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + Fetches the properties of an individual view + and render in the properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + + return ajax_response( + response=result, + status=200 + ) + + @staticmethod + def formatter(result): + """ This function formats output for security label & variables""" + frmtd_result = dict() + sec_lbls = [] + if 'seclabels' in result and result['seclabels'] is not None: + for sec in result['seclabels']: + import re + sec = re.search(r'([^=]+)=(.*$)', sec) + sec_lbls.append({ + 'provider': sec.group(1), + 'security_label': sec.group(2) + }) + + frmtd_result.update({"seclabels": sec_lbls}) + return frmtd_result + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will create a new view object + """ + required_args = [ + 'name', + 'definition' + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + + SQL = self.getSQL(gid, sid, data) + try: + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid): + """ + This function will update a view object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid) + try: + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid): + """ + This function will drop a view object + """ + + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + + # Get name for view from did + SQL = render_template("/".join( + [self.template_path, 'sql/delete.sql']), vid=vid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + + # drop view + SQL = render_template("/".join( + [self.template_path, 'sql/delete.sql']), + nspname=res_data['rows'][0]['nspname'], + name=res_data['rows'][0]['name'], cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid=None): + """ + This function returns modified SQL + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + SQL = self.getSQL(gid, sid, data, vid) + if(SQL): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("-- Nothing changed"), + status=200 + ) + + @staticmethod + def parse_privileges(str_privileges, object_type='VIEW'): + """Parse Privileges.""" + db_privileges = { + 'a': 'INSERT', + 'r': 'SELECT', + 'w': 'UPDATE', + 'd': 'DELETE', + 'D': 'TRUNCATE', + 'x': 'REFERENCES', + 't': 'TRIGGER' + } + privileges = [] + for priv in str_privileges: + priv_with_grant = [] + priv_without_grant = [] + for privilege in priv['privileges']: + if privilege['with_grant']: + priv_with_grant.append( + db_privileges[privilege['privilege_type']]) + elif privilege['privilege']: + priv_without_grant.append( + db_privileges[privilege['privilege_type']]) + + # If we have all acl then just return all + if len(priv_with_grant) == len(db_privileges): + priv_with_grant = ['ALL'] + if len(priv_without_grant) == len(db_privileges): + priv_without_grant = ['ALL'] + + # Server Level validation + if 'grantee' in priv: + privileges.append( + { + 'grantee': priv['grantee'] if 'grantee' in priv else '', + 'with_grant': priv_with_grant, + 'without_grant': priv_without_grant + } + ) + else: + return '' + return privileges + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will generate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + try: + SQL = render_template("/".join( + [self.template_path, 'sql/update.sql']), data=data, + o_data=old_data, conn=self.conn) + except Exception as e: + return internal_server_error(errormsg=str(e)) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_index_column_details(self, idx, data): + """ + This functional will fetch list of column details for index + + Args: + idx: Index OID + data: Properties data + + Returns: + Updated properties data with column details + """ + + self.index_temp_path = 'index' + SQL = render_template("/".join([self.index_temp_path, + 'sql/9.1_plus/column_details.sql']), idx=idx) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + # 'attdef' comes with quotes from query so we need to strip them + # 'options' we need true/false to render switch ASC(false)/DESC(true) + columns = [] + cols = [] + for row in rset['rows']: + + # We need all data as collection for ColumnsModel + cols_data = { + 'colname': row['attdef'].strip('"'), + 'collspcname': row['collnspname'], + 'op_class': row['opcname'], + } + if row['options'][0] == 'DESC': + cols_data['sort_order'] = True + columns.append(cols_data) + + # We need same data as string to display in properties window + # If multiple column then separate it by colon + cols_str = row['attdef'] + if row['collnspname']: + cols_str += ' COLLATE ' + row['collnspname'] + if row['opcname']: + cols_str += ' ' + row['opcname'] + if row['options'][0] == 'DESC': + cols_str += ' DESC' + cols.append(cols_str) + + # Push as collection + data['columns'] = columns + # Push as string + data['cols'] = ', '.join(cols) + + return data + + def get_trigger_column_details(self, tid, clist): + """ + This function will fetch list of column for trigger + + Args: + tid: Table OID + clist: List of columns + + Returns: + Updated properties data with column + """ + + self.trigger_temp_path = 'schema/trigger' + SQL = render_template("/".join([self.trigger_temp_path, + 'get_columns.sql']), + tid=tid, clist=clist) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table used in trigger + columns = [] + + for row in rset['rows']: + columns.append({'column': row['name']}) + + return columns + + def get_rule_sql(self, vid): + """ + Get all non system rules of view node, + generate their sql and render + into sql tab + """ + + self.rule_temp_path = 'rules' + SQL_data = '' + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/properties.sql']), tid=vid) + + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for rule in data['rows']: + + # Generate SQL only for non system rule + if rule['name'] != '_RETURN': + res = [] + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/properties.sql']), + rid=rule['oid'] + ) + status, res = self.conn.execute_dict(SQL) + res = parse_rule_definition(res) + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/create.sql']), + data=res, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + return SQL_data + + def get_trigger_sql(self, vid): + """ + Get all trigger nodes associated with view node, + generate their sql and render + into sql tab + """ + + # Define template path + self.trigger_temp_path = 'trigger' + + SQL_data = '' + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/properties.sql']), + tid=vid) + + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for trigger in data['rows']: + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/properties.sql']), + tid=trigger['oid'], + tgrelid=vid + ) + + status, res = self.conn.execute_dict(SQL) + + res_rows = dict(res['rows'][0]) + if res_rows['tgnargs'] > 1: + # We know that trigger has more than 1 + # arguments, let's join them + res_rows['tgargs'] = ', '.join( + res_rows['tgargs']) + + if len(res_rows['tgattr']) > 1: + columns = ', '.join(res_rows['tgattr'].split(' ')) + res_rows['columns'] = self.get_trigger_column_details( + trigger['oid'], columns) + res_rows = trigger_definition(res_rows) + + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/create.sql']), + data=res_rows, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + + return SQL_data + + def get_index_sql(self, vid): + """ + Get all index associated with view node, + generate their sql and render + into sql tab + """ + + self.index_temp_path = 'index' + SQL_data = '' + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/properties.sql']), tid=vid) + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for index in data['rows']: + res = [] + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/properties.sql']), + idx=index['oid'], + tid=vid + ) + status, res = self.conn.execute_dict(SQL) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = data['nspname'] + data['table'] = data['tabname'] + + # Add column details for current index + data = self.get_index_column_details(index['oid'], data) + + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/create.sql']), + data=data, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + return SQL_data + + @check_precondition + def sql(self, gid, sid, did, scid, vid): + """ + This function will generate sql to render into the sql panel + """ + + SQL_data = '' + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch all privileges for view + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0]['datacl'].append(priv) + else: + res['rows'][0]['datacl'] = [priv] + + result = res['rows'][0] + if 'datacl' in result: + result['datacl'] = self.parse_privileges(result['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), + data=result, + conn=self.conn, + display_comments=True + ) + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=result) + + SQL_data += SQL + SQL_data += self.get_rule_sql(vid) + SQL_data += self.get_trigger_sql(vid) + SQL_data += self.get_index_sql(vid) + + return ajax_response(response=SQL_data) + + @check_precondition + def get_tblspc(self, gid, sid, did, scid): + """ + This function to return list of tablespaces + """ + res = [] + try: + SQL = render_template( + "/".join([self.template_path, 'sql/get_tblspc.sql']) + ) + status, rset = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['spcname'], 'value': row['spcname']} + ) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid): + """ + This function gets the dependents and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependents_result = self.get_dependents(self.conn, vid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid): + """ + This function gets the dependencies and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependencies_result = self.get_dependencies(self.conn, vid) + return ajax_response( + response=dependencies_result, + status=200 + ) + + +class MaterializedViewNode(ViewNode): + """ + This class is responsible for generating routes for + materialized view node. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the MaterializedView + and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * create(gid, sid, did, scid) + - Raise an error - we can not create a material view. + + * update(gid, sid, did, scid) + - This function will update the data for the selected material node + + * delete(self, gid, sid, scid): + - Raise an error - we can not delete a material view. + + * get_sql(data, scid) + - This function will generate sql from model data + + """ + + node_type = materialized_view_blueprint.node_type + + def __init__(self, *args, **kwargs): + """ + Initialize the variables used by methods of ViewNode. + """ + + super(MaterializedViewNode, self).__init__(*args, **kwargs) + + self.template_initial = 'materialized_view' + + @staticmethod + def pg_template_path(ver): + """ + Returns the template path for PostgreSQL servers. + """ + if ver >= 90300: + return 'pg/9.3_plus' + return 'pg/9.3_plus' + + def get_sql(self, gid, sid, data, scid, vid=None): + """ + This function will generate sql from model data + """ + if scid is None: + return bad_request('Cannot create a View!') + + return super(MaterializedViewNode, self).get_sql( + gid, sid, data, scid, vid) + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will genrate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + # merge vacuum lists into one + data['vacuum_data'] = {} + data['vacuum_data']['changed'] = [] + data['vacuum_data']['reset'] = [] + if ('vacuum_table' in data): + if ('changed' in data['vacuum_table']): + for item in data['vacuum_table']['changed']: + if 'value' in item.keys(): + if item['value'] is None: + data['vacuum_data']['reset'].append(item) + else: + data['vacuum_data']['changed'].append(item) + + if ('autovacuum_enabled' in data and + str(data['autovacuum_enabled']).lower() == 'true'): + data['vacuum_data']['changed'].append( + {'name': 'autovacuum_enabled', + 'value': data['autovacuum_enabled']}) + elif (old_data['autovacuum_enabled'] == 'true'): + data['vacuum_data']['changed'].append( + {'name': 'autovacuum_enabled', + 'value': 'true'}) + else: + data['vacuum_data']['changed'] = {} + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + try: + SQL = render_template("/".join( + [self.template_path, 'sql/update.sql']), data=data, + o_data=old_data, conn=self.conn) + except Exception as e: + return internal_server_error(errormsg=str(e)) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + # merge vacuum lists into one + vacuum_table = [item for item in data['vacuum_table'] + if 'value' in item.keys()] + vacuum_toast = [ + {'name': 'toast.'+item['name'], 'value':item['value']} + for item in data['vacuum_toast'] if 'value' in item.keys()] + + # add vacuum_toast dict to vacuum_data only if + # toast's autovacuum is enabled + if ('toast_autovacuum_enabled' in data and + data['toast_autovacuum_enabled'] is True): + data['vacuum_data'] = vacuum_table + vacuum_toast + else: + data['vacuum_data'] = vacuum_table + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_vacuum_defaults(self, gid, sid, did, scid): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + + # returns an array of name & label values + vacuum_fields = render_template("/".join( + [self.template_path, 'vacuum_fields.json'] + )) + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields['table'].keys())+"'" + SQL = render_template("/".join( + [self.template_path, 'sql/vacuum_defaults.sql']), + columns=vacuum_fields_keys) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields['table'][row_name][0] + row['label'] = vacuum_fields['table'][row_name][1] + row['column_type'] = vacuum_fields['table'][row_name][2] + + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def get_vacuum_toast_defaults(self, gid, sid, did, scid): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + + # returns an array of name & label values + vacuum_fields = render_template("/".join( + [self.template_path, 'vacuum_fields.json'] + )) + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields['toast'].keys())+"'" + SQL = render_template("/".join( + [self.template_path, 'sql/vacuum_defaults.sql']), + columns=vacuum_fields_keys) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields['toast'][row_name][0] + row['label'] = vacuum_fields['toast'][row_name][1] + row['column_type'] = vacuum_fields['table'][row_name][2] + + return ajax_response( + response=res['rows'], + status=200 + ) + + def parse_vacuum_data(self, result, type): + """ + Parse vacumm data + """ + + # returns an array of name & label values + vacuum_fields = render_template("/".join( + [self.template_path, 'vacuum_fields.json'] + )) + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields[type].keys()) + "'" + SQL = render_template("/".join( + [self.template_path, 'sql/vacuum_defaults.sql']), + columns=vacuum_fields_keys) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + if type is 'table': + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields[type][row_name][0] + row['label'] = vacuum_fields[type][row_name][1] + row['column_type'] = vacuum_fields[type][row_name][2] + if result[row['name']] is not None: + row['value'] = row['setting'] = float(result[row_name]) + #else: + # row['value'] = -1 + elif type is 'toast': + for row in res['rows']: + row_old_name = row['name'] + row_name = 'toast_{0}'.format(vacuum_fields[type][row_old_name][0]) + row['name'] = vacuum_fields[type][row_old_name][0] + row['label'] = vacuum_fields[type][row_old_name][1] + row['column_type'] = vacuum_fields[type][row_old_name][2] + if result[row_name] and result[row_name] is not None: + row['value'] = row['setting'] = float(result[row_name]) + #else: + # row['value'] = -1 + + return res['rows'] + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + Fetches the properties of an individual view + and render in the properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + + result['vacuum_table'] = self.parse_vacuum_data(result, 'table') + result['vacuum_toast'] = self.parse_vacuum_data(result, 'toast') + + return ajax_response( + response=result, + status=200 + ) + +ViewNode.register_node_view(view_blueprint) +MaterializedViewNode.register_node_view(materialized_view_blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py new file mode 100644 index 0000000..c17539f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py @@ -0,0 +1,12 @@ +""" +We will use the exisiting modules for creating children module under +materialize views. +""" +from pgadmin.browser.server_groups.servers.databases.schemas.tables.columns \ + import blueprint as columns_module +from pgadmin.browser.server_groups.servers.databases.schemas.tables.indexes \ + import blueprint as indexes_modules +from pgadmin.browser.server_groups.servers.databases.schemas.tables.triggers \ + import blueprint as triggers_modules +from pgadmin.browser.server_groups.servers.databases.schemas.tables.rules \ + import blueprint as rules_modules diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-materialized_view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-materialized_view.png new file mode 100644 index 0000000000000000000000000000000000000000..40e65a96e18004104b041b0ae32579b26ae97058 GIT binary patch literal 374 zcmV-+0g3*JP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00009a7bBm000XU z000XU0RWnu7ytkPC`m*?R5;6}lfO#CP!xy1+eV6l)y;y*AP73Rh@jQQr4BxYLpzBe z;^H$1;tQlN;N&6>PA(>hLAOE$2b<YN5@VCx>!1V^ni|PnE{A)5-}!eBlsp2!8Jwv{ z_xxYN&Za98fNAM!r+Elb4K<vFpg}d7qay1FRZB!leDaRNo4Drr^^U#up3r_%Zyrty zMW0`oB8+_iwCrQMIT$PM+D6LOia3pfYk85jS5BDpT2y2muX@kz%fU#$T)=Rmhd1pI zN{>)_xh3TSloEv+sugkCnz_QX^u*q6^b<Q5U_V`EKMrm6egBz3w%Z*9N-2a8n3kS` zXhlcZwT)C6BLS_AeIgkU%MT*y6UP3}1TI)wUL=ftA{hW~`DI$wk61H~McHlU3uk+8 Uv}-=(&;S4c07*qoM6N<$f<!@@p#T5? literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png new file mode 100644 index 0000000000000000000000000000000000000000..078d533684b8bd7e185749c4f1c9620af23d114b GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}Y=BRQE08|>xOdKRnRzD^9{rj2 z_}lVF@Af@;b@}P@d(WRgefI3xqo=p7KVP}!V)Uc`-T(gmd-%V5`%AU||Nn1)p?>Gh zn(Z&tPJa$N{=xgfzqUJn>TdojJ^s=A@Oy`SZ_IYR((AHW{03+NV@Z%-FoVOh8)+a; zlDE5yQ1zZ1Ae(`+z$3C4NPB>>+sSM@AS2(?#W6(Ua%g{~P=f-Gv+27P9V>QJ|4;U6 zbe!GKH+kPSMNh%0YD;wVPp!&p$=H2Qa>4_iC-Ws`4oVu`kXvDIe5l_eV|jwW;+5+S zYgy6~PYdy+-cS45)~k2Z!$WAAUct+ok^krC+MlVs+ppfyc`@F`nC<>l-VMq?t5r)} zBT7;dOH!?pi&B9UgOP!up{{|Eu910&p^25DrIo3vwt<<IfkFPhkew(Ra`RI%(<*Um UkbC@w6{vy1)78&qol`;+0LQMW5dZ)H literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/materialized_view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/materialized_view.png new file mode 100644 index 0000000000000000000000000000000000000000..7b72bdbb024d206a9ccccc307b980af7dc9cea88 GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wk0|O(M zr;B5V#p&b(iHrjr$vHbE{{BC@z1bzxXo_Zv!8F~plmsAX<lOhGZpy*4Te=!QPFFbm zU;Yt~PrZwz#Q!2=gMZH(vj3d_(fm;OaAw=?^=1c;K4AOv`+xgG`_JKRvEsKs+|pZn z;^>D*x6OT+Cpd08n!IM#^LZ1F3M3y)72cH;)3)hB@w(<$ENtuI!+37X&3YKpe7)iO zo7EB_E>059WTrQES9;xXdidD%!K10kJRqPs&0k{Df9H%vu?(M!xtNs?&v|Ub)6*j$ zbyq+v$yxg5%?jQE!}Cc$KEG!_e82v_q}|_|69*1#=yHs8ZfyjDhri#K1C8<Dp#F4o lfA#<Q^_TpMg5)ZIft4K|E4KUpCSXu8c)I$ztaD0e0sxS)lm-9* literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png new file mode 100644 index 0000000000000000000000000000000000000000..44116db56496bb477c8ece7d3fdda666e1b3758b GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE08|>xOdKRnaBToAO4>7 z;On9XZ+AU@dGYD<`_G?0efI3x<EOW7JYRF{Me@Q^nvec>Z-1%w|NsB(FVt^;p7P|y zjoU9bT=|i6;cMdQ&tb<tdSCgTbKzU!@sHlyU#LC&-+lap_k(|JcmCAf{8f7ONA}_O z4*TAi?Rus6RdrP&&|=1tAirP+hi5m^K%69RcNf7&TWxm%Ih+L^k;OpT1B~5HX4?T7 zS)MMAArhC96Am!+_;@g=95{03(5YkR4xT)E^z7l&>_QLRH3SlxI5?abofwq#PpGhn z3y3`waupO1o<6}vaKfZ%6BvUwB`{h}XxPzW6T>ijp+R<{)&<#R3sx-A(NkQ!Xcb#n z<0DHqpYzNNJzLm||5$yx0JK%L#5JNMC9x#cD!C{XNHG{07#ipr8tEDsh8P)GnHXD{ zm}(oCS{WG3d#ASwMMG|WN@iLmM1z4PSOZYCiIssFM8m1+p=*E|7(8A5T-G@yGywnx Cm9(({ literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css new file mode 100644 index 0000000..80b64ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css @@ -0,0 +1,12 @@ +.icon-materialized_view{ + background-image: url('{{ url_for('NODE-materialized_view.static', filename='img/materialized_view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.sql_field_height_280 { + height: 280px; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js new file mode 100644 index 0000000..22c3d3c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js @@ -0,0 +1,450 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + var cellFunction = function(model) { + var self = this, + m = model, + vartype = model.get('column_type'); + + switch(vartype) { + case "integer": + return Backgrid.IntegerCell; + break; + case "number": + return Backgrid.NumberCell; + break; + case "string": + return Backgrid.StringCell; + break; + default: + return Backgrid.Cell; + break; + } + }; + + // Define Security Model with fields and validation + var SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + securitylabel: undefined + }, + schema: [{ + id: 'provider', label: '{{ _("Provider") }}', + type: 'text', editable: true + },{ + id: 'security_label', label: '{{ _("Security Label") }}', + type: 'text', editable: true + }], + + validate: function() { + + // Clear errorModel before validate + this.handler.errorModel.clear(); + + var err = {}, + errmsg = null, + field_name = this.get('provider'), + field_security_label = this.get('security_label'); + + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') === '') { + err['provider'] = '{{ _("Provider cannot be empty.") }}'; + errmsg = errmsg || err['provider']; + this.errorModel.set('provider', errmsg); + return errmsg; + }else{ + this.errorModel.unset('provider'); + } + + if (_.isUndefined(field_security_label) || _.isNull(field_security_label) || + String(field_security_label).replace(/^\s+|\s+$/g, '') === '') { + err['security_label'] = '{{ _("Security Label cannot be empty.") }}'; + errmsg = errmsg || err['security_label']; + this.errorModel.set('security_label', errmsg); + return errmsg; + }else{ + this.errorModel.unset('security_label'); + } + return null; + } + }); + + // Define Security Model with fields and validation + var VacuumTableModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + setting: undefined, + label:undefined, + value: undefined, + column_type: undefined + }, + + toJSON: function(){ + var d = pgAdmin.Browser.Node.Model.prototype.toJSON.apply(this); + delete d.label; + delete d.setting; + delete d.column_type; + return d; + } + }); + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-materialized_view']) { + var materialized_views= pgAdmin.Browser.Nodes['coll-materialized_view'] = + pgAdmin.Browser.Collection.extend({ + node: 'materialized_view', + label: '{{ _("Materialized Views") }}', + type: 'coll-materialized_view', + columns: ['name', 'owner'] + }); + } + + /** + Create and Add a View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['materialized_view']) { + pgAdmin.Browser.Nodes['materialized_view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'materialized_view', + label: '{{ _("Materialized View") }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-materialized_view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-materialized_view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_materialized_view_on_coll', node: 'coll-materialized_view', + module: this, applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_materialized_view', node: 'materialized_view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_materialized_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _("Name") }}', cell: 'string', + type: 'text' + }, + { + id: 'oid', label:'{{ _("OID") }}', cell: 'string', + type: 'text', disabled: true, mode: ['properties'] + }, + { + id: 'owner', label:'{{ _("Owner") }}', cell: 'string', + control: 'node-list-by-name', node: 'role' + }, + { + id: 'schema', label:'{{ _("Schema") }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _("Comment") }}', cell: 'string', + type: 'multiline' + }, + { + id: 'system_view', label:'{{ _("System view?") }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'], + }, + { + id: 'definition', label:'{{ _("") }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], group: 'Definition', + control: Backform.SqlFieldControl, extraClasses:['sql_field_height_280'] + }, + // Add Privilege Control + { + id: 'datacl', label: '{{ _("Privileges") }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _("Security") }}', + mode: ['edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', priority: 3 + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _("Security Labels") }}', + model: SecurityModel, editable: false, type: 'collection', + canEdit: false, group: '{{ _("Security") }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + }, + { + id: 'with_data', label: '{{ _("With Data") }}', + group: '{{ _("Storage") }}', mode: ['edit', 'create'], + type: 'switch', + }, + { + id: 'spcname', label: '{{ _("Tablespace") }}', + editable: false, group: '{{ _("Storage") }}', + mode: ['edit', 'create'], control: 'node-ajax-options', + url: 'get_tblspc', first_empty: true + }, + { + id: 'fillfactor', label: '{{ _("Fill Factor") }}', + group: '{{ _("Storage") }}', mode: ['edit', 'create'], + type: 'integer', min: 10, max: 100 + }, + { + type: 'nested', control: 'tab', id: 'materialization', + label: '{{ _("Materialization") }}', mode: ['edit', 'create'], + group: '{{ _("Storage") }}', + schema:[ + // Define materialized table tab fields + { + id: 'autovacuum_custom', label: '{{ _("Custom auto-vaccum") }}', + group: '{{ _("Table") }}', mode: ['edit', 'create'], + type: 'switch', + }, + { + id: 'autovacuum_enabled', label: '{{ _("Enabled") }}', + group: '{{ _("Table") }}', mode: ['edit', 'create'], + type: 'switch', + deps: ['autovacuum_custom'], + disabled: function(m) { + if(m.has('autovacuum_custom')) { + return !m.get('autovacuum_custom'); + } + return m.isNew(); + } + }, + { + id: 'vacuum_table', label: '{{ _("Vacuum Table") }}', + model: VacuumTableModel, editable: false, type: 'collection', + canEdit: true, group: '{{ _("Table") }}', + mode: ['edit', 'create'], url: 'get_vacuum_defaults', + control: Backform.VacuumCollectionControl.extend({ + grid_columns :[ + { + name: 'label', label: '{{ _("Label") }}', + cell: 'string', editable: false + }, + { + name: 'value', label: '{{ _("Value") }}', + cellFunction: cellFunction, editable: function(m) { + if(m.handler.has('autovacuum_enabled')) { + return m.handler.get('autovacuum_enabled'); + } + return !m.handler.isNew(); + } + }, + { + name: 'setting', label: '{{ _("Default value") }}', + cellFunction: cellFunction, editable: false + } + ] + }), + deps: ['autovacuum_enabled'] + }, + + // Define Toast Table group fields + { + id: 'toast_autovaccum', label: '{{ _("Custom auto-vaccum") }}', + group: '{{ _("Toast Table") }}', mode: ['edit', 'create'], + type: 'switch', + disabled: function(m) { + return !m.isNew(); + } + }, + { + id: 'toast_autovacuum_enabled', label: '{{ _("Enabled") }}', + group: '{{ _("Toast Table") }}', mode: ['edit', 'create'], + type: 'switch', + deps:['toast_autovaccum'], + disabled: function(m) { + if(!m.isNew()) { + return true; + } + else if (m.has('toast_autovaccum')) + return !m.get('toast_autovaccum'); + else + return true; + } + }, + { + id: 'vacuum_toast', label: '{{ _("Vacuum Toast Table") }}', + model: VacuumTableModel, type: 'collection', editable: function(m) { + return m.isNew(); + }, + canEdit: true, group: '{{ _("Toast Table") }}', + mode: ['edit', 'create'], url: 'get_vacuum_toast_defaults', + control: Backform.VacuumCollectionControl.extend({ + grid_columns :[ + { + name: 'label', label: '{{ _("Label") }}', + cell: 'string', editable: false + }, + { + name: 'value', label: '{{ _("Value") }}', + cellFunction: cellFunction, editable: function(m) { + if(!m.handler.isNew()) { + return false; + } + else if(m.handler.has('toast_autovacuum_enabled')) { + return m.handler.get('toast_autovacuum_enabled'); + } + } + }, + { + name: 'setting', label: '{{ _("Default value") }}', + cellFunction: cellFunction, editable: false + } + ] + }), + deps: ['toast_autovacuum_enabled'] + } + + ] + }, + { + id: 'acl', label: '{{ _("ACL") }}', + mode: ['properties'], type: 'text' + }, + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + } + + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check === false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-materialized_view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-materialized_view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..0c1bd6c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql @@ -0,0 +1,41 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, + d.grantor, + d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'm' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql new file mode 100644 index 0000000..03260c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql @@ -0,0 +1,45 @@ +{# ===================== Create new view ===================== #} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if(data.fillfactor or data['autovacuum_enabled'] or data['toast_autovacuum_enabled']) %} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['autovacuum_enabled'] or data['toast_autovacuum_enabled'] or data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['autovacuum_enabled'] %} + autovacuum_enabled = {{ data['autovacuum_enabled']|lower }}{% if data['toast_autovacuum_enabled'] or data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['toast_autovacuum_enabled'] %} + {{ 'toast.autovacuum_enabled' }} = {{ data['toast_autovacuum_enabled']|lower }}{% if data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% for field in data['vacuum_data'] %} +{% if field.value is defined and field.value != '' and field.value != none %} +{% if loop.index > 1%}, +{% endif %} {{ field.name }} = {{ field.value|lower }}{% endif %} +{% endfor %}{{ '\r' }} +) +{% endif %} +{% if data.spcname %}TABLESPACE {{ data.spcname }} +{% endif %} AS +{{ data.definition.rstrip(';') }} +{% if data.with_data %} + WITH DATA; +{% else %} + WITH NO DATA; +{% endif %} +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..cf667fc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# =================== Drop/Cascade materialized view by name ====================#} +{% if vid %} +SELECT + c.relname As name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql new file mode 100644 index 0000000..a8a482c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql @@ -0,0 +1,2 @@ +{# ===== Get list of tablespaces ===== #} +select tblspc.spcname from pg_tablespace tblspc WHERE tblspc.spcname != 'pg_global' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..6cdc8e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ==== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..6411123 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql @@ -0,0 +1,109 @@ +{# ========================== Fetch Materialized View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + c.relispopulated AS with_data, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relacl, + nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, + description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + True AS autovacuum_custom, + True AS toast_autovaccum, + array_to_string(c.relacl::text[], ', ') AS acl, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels, + substring(array_to_string(c.reloptions, ',') + FROM 'fillfactor=([0-9]*)') AS fillfactor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + c.reloptions AS reloptions, tst.reloptions AS toast_reloptions, + (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM + pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'm'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql new file mode 100644 index 0000000..30a9a34 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql @@ -0,0 +1,152 @@ +{# ===================== Update View ===================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{%- if data -%} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{# ===== Rename mat view ===== #} +{% if data.name and data.name != o_data.name %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{# ===== Alter schema view ===== #} +{% if data.schema and data.schema != o_data.schema %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; + +{% endif %} +{# ===== Alter Table owner ===== #} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; + +{% endif %} +{% if data['vacuum_data']['reset']|length > 0 %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( +{% for field in data['vacuum_data']['reset'] %} + {{ field.name }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +) +{% endif %} +{# ===== First Drop and then create mat view ===== #} +{% if def and def != o_data.definition.rstrip(';') %} +{% if data.fillfactor or ( data['vacuum_data']['changed']|length > 0 ) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }}; +CREATE MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']['changed']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['vacuum_data']['changed']|length > 0 %} +{% for field in data['vacuum_data']['changed'] %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +{% endif %} +) + AS +{{ def }} +{% if data.with_data %} + WITH DATA; + +{% else %} + WITH NO DATA; + +{% endif %} +{% endif %} +{% else %} +{# ======= Alter Tablespace ========= #} +{%- if data.spcoid and o_data.spcoid != data.spcoid -%} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET TABLESPACE {{ data.spcoid }}; + +{% endif %} +{# ======= SET/RESET Fillfactor ========= #} +{% if data.fillfactor and o_data.fillfactor != data.fillfactor %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +SET(FILLFACTOR = {{ data.fillfactor }}); + +{% elif data.fillfactor == '' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + FILLFACTOR +); + +{% endif %} +{# ===== Check for with_data property ===== #} +{% if data.with_data is defined and o_data.with_data|lower != data.with_data|lower %} +REFRESH MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} WITH{{ ' NO' if data.with_data|lower == 'false' else '' }} DATA; + +{% endif %} +{# ===== Check for Table tab properties ===== #} +{% if data.autovacuum_custom is defined and data.autovacuum_custom|lower == 'false' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + autovacuum_vacuum_threshold, + autovacuum_analyze_threshold, + autovacuum_vacuum_scale_factor, + autovacuum_analyze_scale_factor, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_freeze_min_age, + autovacuum_freeze_max_age, + autovacuum_freeze_table_age +); + +{% elif(data['vacuum_data']['changed']|length > 0) %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} SET( +{% for field in data['vacuum_data']['changed'] %} +{% if field.value != None %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endif %} +{% endfor %} +); + +{% endif %}{# ===== End check for custom autovaccum ===== #} +{% endif %}{# ===== End block for check data definition ===== #} +{%- if data.comment and data.comment != o_data.comment -%} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; + +{% endif %} +{# ============= The SQL generated below will change privileges ============= #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed -%} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{%- endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# ============== The SQL generated below will change Security Label ========= #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/vacuum_defaults.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/vacuum_defaults.sql new file mode 100644 index 0000000..e60598c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/vacuum_defaults.sql @@ -0,0 +1,2 @@ +{# ============= Fetch list of default values for autovacuum parameters =============== #} +SELECT name, setting::numeric AS setting FROM pg_settings WHERE name IN({{ columns }}) ORDER BY name diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..d192a80 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ==== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/vacuum_fields.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/vacuum_fields.json new file mode 100644 index 0000000..7f8fb28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/vacuum_fields.json @@ -0,0 +1,25 @@ +{# ===== Define name, label and column type of vacuum settings ===== #} +{ + "table": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_analyze_threshold": ["autovacuum_analyze_threshold", "ANALYZE base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_analyze_scale_factor": ["autovacuum_analyze_scale_factor", "ANALYZE scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + }, + "toast": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + } +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..0c1bd6c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql @@ -0,0 +1,41 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, + d.grantor, + d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'm' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql new file mode 100644 index 0000000..d5f1a87 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql @@ -0,0 +1,39 @@ +{# ===================== Create new view ===================== #} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if(data.fillfactor or data['autovacuum_enabled'] or data['toast_autovacuum_enabled']) %} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% for field in data['vacuum_data'] %} +{% if field.value is defined and field.value != '' and field.value != none %} +{% if loop.index > 1%}, +{% endif %} {{ field.name }} = {{ field.value|lower }}{% endif %} +{% endfor %}{{ '\r' }} +) +{% endif %} +{% if data.spcname %}TABLESPACE {{ data.spcname }} +{% endif %} AS +{{ data.definition.rstrip(';') }} +{% if data.with_data %} + WITH DATA; +{% else %} + WITH NO DATA; +{% endif %} +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..cf667fc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# =================== Drop/Cascade materialized view by name ====================#} +{% if vid %} +SELECT + c.relname As name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql new file mode 100644 index 0000000..a8a482c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql @@ -0,0 +1,2 @@ +{# ===== Get list of tablespaces ===== #} +select tblspc.spcname from pg_tablespace tblspc WHERE tblspc.spcname != 'pg_global' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..6cdc8e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ==== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..8546ee2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql @@ -0,0 +1,110 @@ +{# ========================== Fetch Materialized View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + c.relispopulated AS with_data, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relacl, + nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, + description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + True AS autovacuum_custom, + True AS toast_autovaccum, + array_to_string(c.relacl::text[], ', ') AS acl, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels, + substring(array_to_string(c.reloptions, ',') + FROM 'fillfactor=([0-9]*)') AS fillfactor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + c.reloptions AS reloptions, tst.reloptions AS toast_reloptions, + (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable +FROM + pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'm'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql new file mode 100644 index 0000000..df4221d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql @@ -0,0 +1,144 @@ +{# ===================== Update View ===================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{%- if data -%} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{# ===== Rename mat view ===== #} +{% if data.name and data.name != o_data.name %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{# ===== Alter schema view ===== #} +{% if data.schema and data.schema != o_data.schema %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; + +{% endif %} +{# ===== Alter Table owner ===== #} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; + +{% endif %} +{# ===== First Drop and then create mat view ===== #} +{% if def and def != o_data.definition.rstrip(';') %} +{% if data.fillfactor or (data['autovacuum_enabled'] and data['vacuum_data']['changed']|length > 0) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }}; +CREATE MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']['changed']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['vacuum_data']['changed']|length > 0 %} +{% for field in data['vacuum_data']['changed'] %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +{% endif %} +) + AS +{{ def }} +{% if data.with_data %} + WITH DATA; + +{% else %} + WITH NO DATA; + +{% endif %} +{% endif %} +{% else %} +{# ======= Alter Tablespace ========= #} +{%- if data.spcoid and o_data.spcoid != data.spcoid -%} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET TABLESPACE {{ data.spcoid }}; + +{% endif %} +{# ======= SET/RESET Fillfactor ========= #} +{% if data.fillfactor and o_data.fillfactor != data.fillfactor %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +SET(FILLFACTOR = {{ data.fillfactor }}); + +{% elif data.fillfactor == '' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + FILLFACTOR +); + +{% endif %} +{# ===== Check for with_data property ===== #} +{% if data.with_data is defined and o_data.with_data|lower != data.with_data|lower %} +REFRESH MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} WITH{{ ' NO' if data.with_data|lower == 'false' else '' }} DATA; + +{% endif %} +{# ===== Check for Table tab properties ===== #} +{% if data.autovacuum_custom is defined and data.autovacuum_custom|lower == 'false' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + autovacuum_vacuum_threshold, + autovacuum_analyze_threshold, + autovacuum_vacuum_scale_factor, + autovacuum_analyze_scale_factor, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_freeze_min_age, + autovacuum_freeze_max_age, + autovacuum_freeze_table_age +); + +{% elif(data['vacuum_data']['changed']|length > 0) %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} SET( +{% for field in data['vacuum_data']['changed'] %} +{% if field.value != None %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endif %} +{% endfor %} +); + +{% endif %}{# ===== End check for custom autovaccum ===== #} +{% endif %}{# ===== End block for check data definition ===== #} +{%- if data.comment and data.comment != o_data.comment -%} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; + +{% endif %} +{# ============= The SQL generated below will change privileges ============= #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed -%} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{%- endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# ============== The SQL generated below will change Security Label ========= #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/vacuum_settings.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/vacuum_settings.sql new file mode 100644 index 0000000..e60598c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/vacuum_settings.sql @@ -0,0 +1,2 @@ +{# ============= Fetch list of default values for autovacuum parameters =============== #} +SELECT name, setting::numeric AS setting FROM pg_settings WHERE name IN({{ columns }}) ORDER BY name diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..d192a80 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ==== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/vacuum_fields.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/vacuum_fields.json new file mode 100644 index 0000000..7f8fb28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/vacuum_fields.json @@ -0,0 +1,25 @@ +{# ===== Define name, label and column type of vacuum settings ===== #} +{ + "table": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_analyze_threshold": ["autovacuum_analyze_threshold", "ANALYZE base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_analyze_scale_factor": ["autovacuum_analyze_scale_factor", "ANALYZE scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + }, + "toast": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + } +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css new file mode 100644 index 0000000..89eae4e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css @@ -0,0 +1,12 @@ +.icon-view{ + background-image: url('{{ url_for('NODE-view.static', filename='img/view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.sql_field_height_140 { + height: 140px; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js new file mode 100644 index 0000000..2e92154 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js @@ -0,0 +1,304 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + // Define Security Model with fields and validation + var SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + securitylabel: undefined + }, + schema: [{ + id: 'provider', label: '{{ _("Provider") }}', + type: 'text', editable: true + },{ + id: 'security_label', label: '{{ _("Security Label") }}', + type: 'text', editable: true + }], + + validate: function() { + + // Clear errorModel before validate + this.handler.errorModel.clear(); + + var err = {}, + errmsg = null, + field_name = this.get('provider'), + field_security_label = this.get('security_label'); + + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['provider'] = '{{ _("Provider cannot be empty.") }}'; + errmsg = errmsg || err['provider']; + this.errorModel.set('provider', errmsg); + return errmsg; + }else{ + this.errorModel.unset('provider'); + } + + if (_.isUndefined(field_security_label) || _.isNull(field_security_label) || + String(field_security_label).replace(/^\s+|\s+$/g, '') == '') { + err['security_label'] = '{{ _("Security Label cannot be empty.") }}'; + errmsg = errmsg || err['security_label']; + this.errorModel.set('security_label', errmsg); + return errmsg; + }else{ + this.errorModel.unset('security_label'); + } + return null; + } + }); + + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-view']) { + var views= pgAdmin.Browser.Nodes['coll-view'] = + pgAdmin.Browser.Collection.extend({ + node: 'view', + label: '{{ _("Views") }}', + type: 'coll-view', + columns: ["name", "owner"] + }); + } + + /** + Create and Add a View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['view']) { + pgAdmin.Browser.Nodes['view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'view', + label: '{{ _('View') }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_view_on_coll', node: 'coll-view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('View...') }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text' + }, + { + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text', disabled: true, mode: ['properties'] + }, + { + id: 'owner', label:'{{ _('Owner') }}', cell: 'string', control: 'node-list-by-name', + node: 'role' + }, + { + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline' + }, + { + id: 'system_view', label:'{{ _('System view?') }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'] + }, + { + id: 'security_barrier', label:'{{ _('Security Barrier') }}', cell: 'string', + type: 'switch', min_version: '90200', mode: ['create', 'edit'], + group: 'Definition' + }, + { + id: 'check_option', label:'{{ _('Check Options') }}', + control: 'select2', group: 'Definition', type: 'text', + min_version: '90400', mode:['properties', 'create', 'edit'], + select2: { + + // set select2 option width to 100% + width: '100%' + }, + options:[ + {label: "No", value: "no"}, + {label: "Local", value: "local"}, + {label: "Cascaded", value: "cascaded"} + ] + }, + { + id: 'definition', label:'{{ _('Definition') }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], group: 'Definition', + control: Backform.SqlFieldControl, extraClasses:['sql_field_height_140'] + }, + { + id: 'acl', label: '{{ _("ACL") }}', + mode: ['properties'], type: 'text' + }, + + // Add Privilege Control + { + id: 'datacl', label: '{{ _('Privileges') }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _('Security') }}', + mode: ['edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _('Security Labels') }}', + model: SecurityModel, editable: false, type: 'collection', + canEdit: false, group: '{{ _('Security') }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + }, + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + } + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql new file mode 100644 index 0000000..5850639 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql @@ -0,0 +1,51 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT + (d).grantee AS grantee, + (d).grantor AS grantor, + (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM + (SELECT + aclexplode(relacl) AS d + FROM + pg_class cl1 + WHERE + cl1.oid = {{ vid }} + ) a + ) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql new file mode 100644 index 0000000..d54fff9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql @@ -0,0 +1,21 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql new file mode 100644 index 0000000..ce1d801 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql @@ -0,0 +1,69 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + nsp.nspname AS schema, + description AS comment, + c.reltablespace AS spcoid, + pg_get_userbyid(c.relowner) AS owner, + pg_get_viewdef(c.oid, true) AS definition, + array_to_string(c.relacl::text[], ', ') AS acl, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + (SELECT + array_agg(label) + FROM + pg_seclabels sl1 + WHERE + sl1.objoid=c.oid AND sl1.objsubid=0 + ) AS labels, + (SELECT + array_agg(provider) + FROM + pg_seclabels sl2 + WHERE sl2.objoid=c.oid AND sl2.objsubid=0 + ) AS providers +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +WHERE ((c.relhasrules + AND + (EXISTS( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) AND (bpchar(r.ev_type) = '1'::bpchar)) + )) + ) AND (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql new file mode 100644 index 0000000..2f7f82e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql @@ -0,0 +1,66 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} + AS{{ def }}; +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql new file mode 100644 index 0000000..5f2d0fb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql @@ -0,0 +1,52 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT + (d).grantee AS grantee, + (d).grantor AS grantor, + (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM + (SELECT + aclexplode(relacl) AS d + FROM + pg_class cl1 + WHERE + cl1.oid = {{ vid }} + ) a + ) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql new file mode 100644 index 0000000..eebe0f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql @@ -0,0 +1,70 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + pg_get_userbyid(c.relowner) AS owner, + description As comment, + pg_get_viewdef(c.oid, true) AS definition, + nsp.nspname AS schema, + array_to_string(c.relacl::text[], ', ') AS acl, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar) + ) )) + ) AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql new file mode 100644 index 0000000..c37d4ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql @@ -0,0 +1,74 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) {% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..84300cf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql @@ -0,0 +1,38 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..5d3d1e4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql @@ -0,0 +1,74 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + nsp.nspname AS schema, + c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql new file mode 100644 index 0000000..02d8f37 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql @@ -0,0 +1,75 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql new file mode 100644 index 0000000..7d791f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql @@ -0,0 +1,39 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql new file mode 100644 index 0000000..191090d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier }} +{% if data.security_barrier %} security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner and data.m_view is undefined %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql new file mode 100644 index 0000000..c7380d8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql @@ -0,0 +1,77 @@ +{# ========================== Fetch View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + c.reltablespace AS spcoid, + nsp.nspname AS schema, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'check_option=([a-z]*)') AS check_option, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql new file mode 100644 index 0000000..56183fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql @@ -0,0 +1,82 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql new file mode 100644 index 0000000..ef27594 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ===== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql new file mode 100644 index 0000000..7d791f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql @@ -0,0 +1,39 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql new file mode 100644 index 0000000..191090d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier }} +{% if data.security_barrier %} security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner and data.m_view is undefined %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql new file mode 100644 index 0000000..2af1252 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql @@ -0,0 +1,76 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + c.reltablespace AS spcoid, + nsp.nspname AS schema, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'check_option=([a-z]*)') AS check_option, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql new file mode 100644 index 0000000..56183fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql @@ -0,0 +1,82 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql new file mode 100644 index 0000000..ef27594 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ===== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: [pgAdmin4] PATCH: View and Role Node @ 2016-04-11 11:49 Surinder Kumar <[email protected]> parent: Surinder Kumar <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Surinder Kumar @ 2016-04-11 11:49 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: pgadmin-hackers Hi, PFA patch with following changes: 1. Modified the materialized view js & py files as its common functions for table are now moved into schema.js and utils.py. 2. Removed whitespaces from code. Also, this patch is now also dependent on the patch "Disable PrivilegeControl for nodes visible under catalog" I sent earlier today. Thanks On Wed, Mar 23, 2016 at 9:14 PM, Surinder Kumar < [email protected]> wrote: > > This patch contains following nodes: > 1. View Node > 2. Materialized View Node > > This patch depends on couple of patches: > 1. Control for displaying "auto vacuum" fields into grid > 2. Tables sub node patch. > > First apply these two patches then apply patch this patch. > > Please review the patch. > > On Tue, Mar 1, 2016 at 10:42 PM, Dave Page <[email protected]> wrote: > >> Sorry - I believe this is it (I had to rescue it from the trash). >> >> On Tue, Mar 1, 2016 at 5:16 AM, Surinder Kumar >> <[email protected]> wrote: >> > Dave you forgot to attach patch. Can you please attach it ? >> > >> > On Thu, Feb 25, 2016 at 4:33 PM, Dave Page <[email protected]> wrote: >> >> >> >> Hi >> >> >> >> On Wed, Feb 24, 2016 at 1:31 PM, Surinder Kumar >> >> <[email protected]> wrote: >> >>> >> >>> Hi, >> >>> >> >>> Please find the updated patch with following changes: >> >>> >> >>> 1. Integrated dependents and dependency tabs with view and rule node. >> >>> 2. Added "Create Rule" option for Table node. >> >>> 3. Added node_inode property for rule node as it has no child node. >> >>> 4. Improved javascript code commenting and added pydoc in python >> files. >> >>> 5. Name column under dependents tab showing wrong name for rule node >> >>> due to typo mistake, It is fixed. >> >>> >> >>> Please review it and Let me know for any comments. >> >> >> >> >> >> Please find an updated patch attached, in which I tweaked some comments >> >> and fixed a few minor issues as I reviewed the code. The following >> issues >> >> need to be addressed please, then I'll take another look: >> >> >> >> - Don't show the definition SQL in the main properties tab. >> > Done > >> >> >> >> - Check Options is missing from the properties tab. >> > Added > >> >> >> >> - Views were made function in PostgreSQL v6.4! Why only load the module >> >> for 9.1+? >> > Fixed > >> >> >> >> - The TRUNCATE ACL flag is not recognised, leading to ACLs being shown >> as: >> >> arwdUNKNOWNxt >> > Fixed > >> >> >> >> - 4 character indents are not applied consistently to SQL templates >> > Done > >> >> >> >> - Why do servers pre-9.3 have VACUUM options in the SQL templates? >> > Done > >> >> >> >> - There should be no special casing of Slony schemas >> (backend_support.sql) >> > Done > >> >> >> >> - Don't include \'s on line ends of multiline comments or docstrings. >> > Done > >> >> >> >> - I can't create a Materialised View >> > This patch contains materialized view now > >> >> >> >> On the dialogs: >> >> >> >> - The "Definition" input box should have a border. >> > Fixed > >> >> >> >> - The "Definition" input box fill the vertical space assigned to it, >> >> such that the border on the left runs top to bottom (see the main SQL >> tab). >> > Fixed > >> >> >> >> - The "Definition" input box should be displayed below the label at >> all >> >> times (unlike other controls), to maximise available space >> > Fixed > >> >> >> >> - The SQL tab view is broken for views (tested on PG 9.4), likely due >> to >> >> the TRUNCATE ACL issue: >> >> >> >> 2016-02-25 10:59:00,118: INFO werkzeug: 127.0.0.1 - - [25/Feb/2016 >> >> 10:59:00] "GET /browser/view/sql/1/1/24587/27424/27555 HTTP/1.1" 500 - >> >> Traceback (most recent call last): >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1836, in __call__ >> >> return self.wsgi_app(environ, start_response) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1820, in wsgi_app >> >> response = self.make_response(self.handle_exception(e)) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1403, in handle_exception >> >> reraise(exc_type, exc_value, tb) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1817, in wsgi_app >> >> response = self.full_dispatch_request() >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1477, in full_dispatch_request >> >> rv = self.handle_user_exception(e) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1381, in handle_user_exception >> >> reraise(exc_type, exc_value, tb) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1475, in full_dispatch_request >> >> rv = self.dispatch_request() >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/app.py", >> >> line 1461, in dispatch_request >> >> return self.view_functions[rule.endpoint](**req.view_args) >> >> File >> >> >> "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/flask/views.py", >> >> line 84, in view >> >> return self.dispatch_request(*args, **kwargs) >> >> File "/Users/dpage/git/pgadmin4/web/pgadmin/browser/utils.py", line >> 248, >> >> in dispatch_request >> >> return method(*args, **kwargs) >> >> File >> >> >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> >> line 242, in wrap >> >> return f(*args, **kwargs) >> >> File >> >> >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> >> line 646, in sql >> >> result['datacl'] = self.parse_privileges(result['datacl']) >> >> File >> >> >> "/Users/dpage/git/pgadmin4/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py", >> >> line 540, in parse_privileges >> >> db_privileges[privilege['privilege_type']]) >> >> KeyError: 'UNKNOWN' >> >> >> > Done > >> >> >> >> Thanks! >> >> >> >> -- >> >> Dave Page >> >> Blog: http://pgsnake.blogspot.com >> >> Twitter: @pgsnake >> >> >> >> EnterpriseDB UK: http://www.enterprisedb.com >> >> The Enterprise PostgreSQL Company >> > >> > >> >> >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EnterpriseDB UK: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> > > -- Sent via pgadmin-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers Attachments: [application/octet-stream] view_material_view_node_patch_v5.patch (197.2K, 3-view_material_view_node_patch_v5.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py new file mode 100644 index 0000000..4583100 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -0,0 +1,1236 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements View and Materialized View Node""" + +import simplejson as json +from flask import render_template, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error, \ + bad_request +from pgadmin.browser.utils import PGChildNodeView +import pgadmin.browser.server_groups.servers as servers +from pgadmin.browser.server_groups.servers.databases.schemas.utils import \ + SchemaChildModule, parse_rule_definition, VacuumSettings +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps + + +""" + This module is responsible for generating two nodes + 1) View + 2) Materialized View + + We have created single file because view & material view has same + functionality. It allows us to share the same submodules for both + view, and material view modules. + + This modules uses separate template paths for each respective node + - templates/view for View node + - templates/materialized_view for MaterializedView node + + [Each path contains node specific js files as well as sql template files.] +""" + + +class ViewModule(SchemaChildModule): + """ + class ViewModule(SchemaChildModule): + + A module class for View node derived from SchemaChildModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the View and it's base module. + + * get_nodes(gid, sid, did, scid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for View, when any of the server node is + initialized. + """ + NODE_TYPE = 'view' + COLLECTION_LABEL = gettext("Views") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ViewModule and it's base module. + + Args: + *args: + **kwargs: + """ + super(ViewModule, self).__init__(*args, **kwargs) + self.min_ver = None + self.max_ver = None + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for views, when any database node is + initialized, as views are under schemas. + """ + return servers.ServerModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "view/css/view.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "materialized_view/css/materialized_view.css", + node_type='materialized_view', + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +class MaterializedViewModule(ViewModule): + """ + class MaterializedViewModule(ViewModule) + A module class for the materialized view and view node derived + from ViewModule. + """ + + NODE_TYPE = 'materialized_view' + COLLECTION_LABEL = gettext("Materialized Views") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the MaterializedViewModule and + it's base module. + + Args: + *args: + **kwargs: + """ + super(MaterializedViewModule, self).__init__(*args, **kwargs) + self.min_ver = 90300 + self.max_ver = None + +view_blueprint = ViewModule(__name__) +materialized_view_blueprint = MaterializedViewModule( + __name__) + + +def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to instance of the method. + + Assumptions: + This function will always be used as decorator of a class method. + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext("Connection to the server has been lost!") + ) + self.datlastsysoid = self.manager.db_info[ + kwargs['did']]['datlastsysoid'] + + # Set template path for sql scripts + self.template_path = self.template_initial + '/' + ( + self.ppas_template_path(self.manager.version) + if self.manager.server_type == 'ppas' else + self.pg_template_path(self.manager.version) + ) + + return f(*args, **kwargs) + + return wrap + + +class ViewNode(PGChildNodeView): + """ + This class is responsible for generating routes for view node. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the ViewNode and it's base view. + + * module_js() + - Request handler for module.js routes for the view node module + javascript, which returns javscript for this module. + + * list() + - This function is used to list all the view nodes within the + collection. + + * nodes() + - This function will used to create all the child node within the + collection, Here it will create all the view node. + + * properties(gid, sid, did, scid) + - This function will show the properties of the selected view node. + + * create(gid, sid, did, scid) + - This function will create the new view object. + + * update(gid, sid, did, scid) + - This function will update the data for the selected view node. + + * delete(self, gid, sid, scid): + - This function will drop the view object + + * msql(gid, sid, did, scid) + - This function is used to return modified SQL for the selected view + node. + + * get_sql(data, scid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid): + - This function will generate sql to show it in sql pane for the view + node. + + * dependency(gid, sid, did, scid): + - This function will generate dependency list show it in dependency + pane for the selected view node. + + * dependent(gid, sid, did, scid): + - This function will generate dependent list to show it in dependent + pane for the selected view node. + """ + node_type = view_blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'vid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}], + 'get_tblspc': [{'get': 'get_tblspc'}, {'get': 'get_tblspc'}], + 'get_table_vacuum': [ + {'get': 'get_table_vacuum'}, + {'get': 'get_table_vacuum'}], + 'get_toast_table_vacuum': [ + {'get': 'get_toast_table_vacuum'}, + {'get': 'get_toast_table_vacuum'}] + }) + + def __init__(self, *args, **kwargs): + """ + Initialize the variables used by methods of ViewNode. + """ + + super(ViewNode, self).__init__(*args, **kwargs) + + self.manager = None + self.conn = None + self.template_path = None + self.template_initial = 'view' + + @staticmethod + def ppas_template_path(ver): + """ + Returns the template path for PPAS servers. + """ + return 'ppas/9.3_plus' + + @staticmethod + def pg_template_path(ver): + """ + Returns the template path for PostgreSQL servers. + """ + if ver >= 90400: + return 'pg/9.4_plus' + elif ver >= 90300: + return 'pg/9.3_plus' + elif ver >= 90200: + return 'pg/9.2_plus' + elif ver >= 90100: + return 'pg/9.1_plus' + return 'pg/9.1_plus' + + @check_precondition + def list(self, gid, sid, did, scid): + """ + Fetches all views properties and render into properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), scid=scid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + Lists all views under the Views Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + scid, + row['name'], + icon="icon-view" + )) + + return make_json_response( + data=res, + status=200 + ) + + def parse_views_privileges(self, db_privileges): + """ + This function forms a separate list of grantable + and non grantable privileges. + """ + acl = {'grantor': db_privileges['grantor'], + 'grantee': db_privileges['grantee'], + 'privileges': [] + } + + privileges = [] + for idx, priv in enumerate(db_privileges['privileges']): + if db_privileges['grantable'][idx]: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": True + }) + else: + privileges.append({"privilege_type": priv, + "privilege": True, + "with_grant": False + }) + + acl['privileges'] = privileges + + return acl + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + Fetches the properties of an individual view + and render in the properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + + return ajax_response( + response=result, + status=200 + ) + + @staticmethod + def formatter(result): + """ This function formats output for security label & variables""" + frmtd_result = dict() + sec_lbls = [] + if 'seclabels' in result and result['seclabels'] is not None: + for sec in result['seclabels']: + import re + sec = re.search(r'([^=]+)=(.*$)', sec) + sec_lbls.append({ + 'provider': sec.group(1), + 'security_label': sec.group(2) + }) + + frmtd_result.update({"seclabels": sec_lbls}) + return frmtd_result + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will create a new view object + """ + required_args = [ + 'name', + 'definition' + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + + SQL = self.getSQL(gid, sid, data) + try: + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, vid): + """ + This function will update a view object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, vid) + try: + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/view_id.sql']), data=data) + status, view_id = self.conn.execute_scalar(SQL) + return jsonify( + node=self.blueprint.generate_browser_node( + view_id, + scid, + data['name'], + icon="icon-view" + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, vid): + """ + This function will drop a view object + """ + + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + + # Get name for view from did + SQL = render_template("/".join( + [self.template_path, 'sql/delete.sql']), vid=vid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + + # drop view + SQL = render_template("/".join( + [self.template_path, 'sql/delete.sql']), + nspname=res_data['rows'][0]['nspname'], + name=res_data['rows'][0]['name'], cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("View dropped"), + data={ + 'id': vid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, vid=None): + """ + This function returns modified SQL + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + SQL = self.getSQL(gid, sid, data, vid) + if(SQL): + return make_json_response( + data=SQL, + status=200 + ) + else: + return make_json_response( + data=gettext("-- Nothing changed"), + status=200 + ) + + @staticmethod + def parse_privileges(str_privileges, object_type='VIEW'): + """Parse Privileges.""" + db_privileges = { + 'a': 'INSERT', + 'r': 'SELECT', + 'w': 'UPDATE', + 'd': 'DELETE', + 'D': 'TRUNCATE', + 'x': 'REFERENCES', + 't': 'TRIGGER' + } + privileges = [] + for priv in str_privileges: + priv_with_grant = [] + priv_without_grant = [] + for privilege in priv['privileges']: + if privilege['with_grant']: + priv_with_grant.append( + db_privileges[privilege['privilege_type']]) + elif privilege['privilege']: + priv_without_grant.append( + db_privileges[privilege['privilege_type']]) + + # If we have all acl then just return all + if len(priv_with_grant) == len(db_privileges): + priv_with_grant = ['ALL'] + if len(priv_without_grant) == len(db_privileges): + priv_without_grant = ['ALL'] + + # Server Level validation + if 'grantee' in priv: + privileges.append( + { + 'grantee': priv['grantee'] if 'grantee' in priv else '', + 'with_grant': priv_with_grant, + 'without_grant': priv_without_grant + } + ) + else: + return '' + return privileges + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will generate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + try: + SQL = render_template("/".join( + [self.template_path, 'sql/update.sql']), data=data, + o_data=old_data, conn=self.conn) + except Exception as e: + return internal_server_error(errormsg=str(e)) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_index_column_details(self, idx, data): + """ + This functional will fetch list of column details for index + + Args: + idx: Index OID + data: Properties data + + Returns: + Updated properties data with column details + """ + + self.index_temp_path = 'index' + SQL = render_template("/".join([self.index_temp_path, + 'sql/9.1_plus/column_details.sql']), idx=idx) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + # 'attdef' comes with quotes from query so we need to strip them + # 'options' we need true/false to render switch ASC(false)/DESC(true) + columns = [] + cols = [] + for row in rset['rows']: + + # We need all data as collection for ColumnsModel + cols_data = { + 'colname': row['attdef'].strip('"'), + 'collspcname': row['collnspname'], + 'op_class': row['opcname'], + } + if row['options'][0] == 'DESC': + cols_data['sort_order'] = True + columns.append(cols_data) + + # We need same data as string to display in properties window + # If multiple column then separate it by colon + cols_str = row['attdef'] + if row['collnspname']: + cols_str += ' COLLATE ' + row['collnspname'] + if row['opcname']: + cols_str += ' ' + row['opcname'] + if row['options'][0] == 'DESC': + cols_str += ' DESC' + cols.append(cols_str) + + # Push as collection + data['columns'] = columns + # Push as string + data['cols'] = ', '.join(cols) + + return data + + def get_trigger_column_details(self, tid, clist): + """ + This function will fetch list of column for trigger + + Args: + tid: Table OID + clist: List of columns + + Returns: + Updated properties data with column + """ + + self.trigger_temp_path = 'schema/trigger' + SQL = render_template("/".join([self.trigger_temp_path, + 'get_columns.sql']), + tid=tid, clist=clist) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table used in trigger + columns = [] + + for row in rset['rows']: + columns.append({'column': row['name']}) + + return columns + + def get_rule_sql(self, vid): + """ + Get all non system rules of view node, + generate their sql and render + into sql tab + """ + + self.rule_temp_path = 'rules' + SQL_data = '' + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/properties.sql']), tid=vid) + + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for rule in data['rows']: + + # Generate SQL only for non system rule + if rule['name'] != '_RETURN': + res = [] + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/properties.sql']), + rid=rule['oid'] + ) + status, res = self.conn.execute_dict(SQL) + res = parse_rule_definition(res) + SQL = render_template("/".join( + [self.rule_temp_path, 'sql/create.sql']), + data=res, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + return SQL_data + + def get_trigger_sql(self, vid): + """ + Get all trigger nodes associated with view node, + generate their sql and render + into sql tab + """ + + # Define template path + self.trigger_temp_path = 'trigger' + + SQL_data = '' + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/properties.sql']), + tid=vid) + + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for trigger in data['rows']: + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/properties.sql']), + tid=trigger['oid'], + tgrelid=vid + ) + + status, res = self.conn.execute_dict(SQL) + + res_rows = dict(res['rows'][0]) + if res_rows['tgnargs'] > 1: + # We know that trigger has more than 1 + # arguments, let's join them + res_rows['tgargs'] = ', '.join( + res_rows['tgargs']) + + if len(res_rows['tgattr']) > 1: + columns = ', '.join(res_rows['tgattr'].split(' ')) + res_rows['columns'] = self.get_trigger_column_details( + trigger['oid'], columns) + res_rows = trigger_definition(res_rows) + + SQL = render_template("/".join( + [self.trigger_temp_path, 'sql/9.1_plus/create.sql']), + data=res_rows, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + + return SQL_data + + def get_index_sql(self, vid): + """ + Get all index associated with view node, + generate their sql and render + into sql tab + """ + + self.index_temp_path = 'index' + SQL_data = '' + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/properties.sql']), tid=vid) + status, data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=data) + + for index in data['rows']: + res = [] + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/properties.sql']), + idx=index['oid'], + tid=vid + ) + status, res = self.conn.execute_dict(SQL) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = data['nspname'] + data['table'] = data['tabname'] + + # Add column details for current index + data = self.get_index_column_details(index['oid'], data) + + SQL = render_template("/".join( + [self.index_temp_path, 'sql/9.1_plus/create.sql']), + data=data, display_comments=True) + SQL_data += '\n' + SQL_data += SQL + return SQL_data + + @check_precondition + def sql(self, gid, sid, did, scid, vid): + """ + This function will generate sql to render into the sql panel + """ + + SQL_data = '' + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch all privileges for view + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0]['datacl'].append(priv) + else: + res['rows'][0]['datacl'] = [priv] + + result = res['rows'][0] + if 'datacl' in result: + result['datacl'] = self.parse_privileges(result['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), + data=result, + conn=self.conn, + display_comments=True + ) + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=result) + + SQL_data += SQL + SQL_data += self.get_rule_sql(vid) + SQL_data += self.get_trigger_sql(vid) + SQL_data += self.get_index_sql(vid) + + return ajax_response(response=SQL_data) + + @check_precondition + def get_tblspc(self, gid, sid, did, scid): + """ + This function to return list of tablespaces + """ + res = [] + try: + SQL = render_template( + "/".join([self.template_path, 'sql/get_tblspc.sql']) + ) + status, rset = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['spcname'], 'value': row['spcname']} + ) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, vid): + """ + This function gets the dependents and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependents_result = self.get_dependents(self.conn, vid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, vid): + """ + This function gets the dependencies and returns an ajax response + for the view node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + vid: View ID + """ + dependencies_result = self.get_dependencies(self.conn, vid) + return ajax_response( + response=dependencies_result, + status=200 + ) + + +class MaterializedViewNode(ViewNode, VacuumSettings): + """ + This class is responsible for generating routes for + materialized view node. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the MaterializedView + and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * create(gid, sid, did, scid) + - Raise an error - we can not create a material view. + + * update(gid, sid, did, scid) + - This function will update the data for the selected material node + + * delete(self, gid, sid, scid): + - Raise an error - we can not delete a material view. + + * get_sql(data, scid) + - This function will generate sql from model data + + """ + + node_type = materialized_view_blueprint.node_type + + def __init__(self, *args, **kwargs): + """ + Initialize the variables used by methods of ViewNode. + """ + + super(MaterializedViewNode, self).__init__(*args, **kwargs) + + self.template_initial = 'materialized_view' + + @staticmethod + def pg_template_path(ver): + """ + Returns the template path for PostgreSQL servers. + """ + if ver >= 90300: + return 'pg/9.3_plus' + return 'pg/9.3_plus' + + def get_sql(self, gid, sid, data, scid, vid=None): + """ + This function will generate sql from model data + """ + if scid is None: + return bad_request('Cannot create a View!') + + return super(MaterializedViewNode, self).get_sql( + gid, sid, data, scid, vid) + + def getSQL(self, gid, sid, data, vid=None): + """ + This function will genrate sql from model data + """ + try: + if vid is not None: + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql']), + vid=vid, + datlastsysoid=self.datlastsysoid + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + if 'name' not in data: + data['name'] = res['rows'][0]['name'] + if 'schema' not in data: + data['schema'] = res['rows'][0]['schema'] + + # merge vacuum lists into one + data['vacuum_data'] = {} + data['vacuum_data']['changed'] = [] + data['vacuum_data']['reset'] = [] + if ('vacuum_table' in data): + if ('changed' in data['vacuum_table']): + for item in data['vacuum_table']['changed']: + if 'value' in item.keys(): + if item['value'] is None: + data['vacuum_data']['reset'].append(item) + else: + data['vacuum_data']['changed'].append(item) + + if ('autovacuum_enabled' in data and + str(data['autovacuum_enabled']).lower() == 'true'): + data['vacuum_data']['changed'].append( + {'name': 'autovacuum_enabled', + 'value': data['autovacuum_enabled']}) + elif (old_data['autovacuum_enabled'] == 'true'): + data['vacuum_data']['changed'].append( + {'name': 'autovacuum_enabled', + 'value': 'true'}) + else: + data['vacuum_data']['changed'] = {} + + key = 'datacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = self.parse_privileges( + data[key]['added']) + if 'changed' in data[key]: + data[key]['changed'] = self.parse_privileges( + data[key]['changed']) + if 'deleted' in data[key]: + data[key]['deleted'] = self.parse_privileges( + data[key]['deleted']) + try: + SQL = render_template("/".join( + [self.template_path, 'sql/update.sql']), data=data, + o_data=old_data, conn=self.conn) + except Exception as e: + return internal_server_error(errormsg=str(e)) + else: + required_args = [ + 'name', + 'schema', + 'definition' + ] + for arg in required_args: + if arg not in data: + return " -- definition incomplete" + + # merge vacuum lists into one + vacuum_table = [item for item in data['vacuum_table'] + if 'value' in item.keys()] + vacuum_toast = [ + {'name': 'toast.'+item['name'], 'value':item['value']} + for item in data['vacuum_toast'] if 'value' in item.keys()] + + # add vacuum_toast dict to vacuum_data only if + # toast's autovacuum is enabled + if ('toast_autovacuum_enabled' in data and + data['toast_autovacuum_enabled'] is True): + data['vacuum_data'] = vacuum_table + vacuum_toast + else: + data['vacuum_data'] = vacuum_table + + if 'datacl' in data and data['datacl'] is not None: + data['datacl'] = self.parse_privileges(data['datacl']) + SQL = render_template("/".join( + [self.template_path, 'sql/create.sql']), data=data) + if data['definition']: + SQL += "\n" + SQL += render_template("/".join( + [self.template_path, 'sql/grant.sql']), data=data) + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_table_vacuum(self, gid, sid, did, scid): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + + res = self.get_vacuum_table_settings(self.conn) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def get_toast_table_vacuum(self, gid, sid, did, scid): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + res = self.get_vacuum_toast_settings(self.conn) + + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, vid): + """ + Fetches the properties of an individual view + and render in the properties tab + """ + SQL = render_template("/".join( + [self.template_path, 'sql/properties.sql'] + ), vid=vid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template("/".join( + [self.template_path, 'sql/acl.sql']), vid=vid) + status, dataclres = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in dataclres['rows']: + priv = self.parse_views_privileges(row) + if row['deftype'] in res['rows'][0]: + res['rows'][0][row['deftype']].append(priv) + else: + res['rows'][0][row['deftype']] = [priv] + + result = res['rows'][0] + + # sending result to formtter + frmtd_reslt = self.formatter(result) + + # merging formated result with main result again + result.update(frmtd_reslt) + + result['vacuum_table'] = self.parse_vacuum_data( + self.conn, result, 'table') + result['vacuum_toast'] = self.parse_vacuum_data( + self.conn, result, 'toast') + + return ajax_response( + response=result, + status=200 + ) + +ViewNode.register_node_view(view_blueprint) +MaterializedViewNode.register_node_view(materialized_view_blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py new file mode 100644 index 0000000..c17539f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/children/__init__.py @@ -0,0 +1,12 @@ +""" +We will use the exisiting modules for creating children module under +materialize views. +""" +from pgadmin.browser.server_groups.servers.databases.schemas.tables.columns \ + import blueprint as columns_module +from pgadmin.browser.server_groups.servers.databases.schemas.tables.indexes \ + import blueprint as indexes_modules +from pgadmin.browser.server_groups.servers.databases.schemas.tables.triggers \ + import blueprint as triggers_modules +from pgadmin.browser.server_groups.servers.databases.schemas.tables.rules \ + import blueprint as rules_modules diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-materialized_view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-materialized_view.png new file mode 100644 index 0000000000000000000000000000000000000000..40e65a96e18004104b041b0ae32579b26ae97058 GIT binary patch literal 374 zcmV-+0g3*JP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00009a7bBm000XU z000XU0RWnu7ytkPC`m*?R5;6}lfO#CP!xy1+eV6l)y;y*AP73Rh@jQQr4BxYLpzBe z;^H$1;tQlN;N&6>PA(>hLAOE$2b<YN5@VCx>!1V^ni|PnE{A)5-}!eBlsp2!8Jwv{ z_xxYN&Za98fNAM!r+Elb4K<vFpg}d7qay1FRZB!leDaRNo4Drr^^U#up3r_%Zyrty zMW0`oB8+_iwCrQMIT$PM+D6LOia3pfYk85jS5BDpT2y2muX@kz%fU#$T)=Rmhd1pI zN{>)_xh3TSloEv+sugkCnz_QX^u*q6^b<Q5U_V`EKMrm6egBz3w%Z*9N-2a8n3kS` zXhlcZwT)C6BLS_AeIgkU%MT*y6UP3}1TI)wUL=ftA{hW~`DI$wk61H~McHlU3uk+8 Uv}-=(&;S4c07*qoM6N<$f<!@@p#T5? literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/coll-view.png new file mode 100644 index 0000000000000000000000000000000000000000..078d533684b8bd7e185749c4f1c9620af23d114b GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}Y=BRQE08|>xOdKRnRzD^9{rj2 z_}lVF@Af@;b@}P@d(WRgefI3xqo=p7KVP}!V)Uc`-T(gmd-%V5`%AU||Nn1)p?>Gh zn(Z&tPJa$N{=xgfzqUJn>TdojJ^s=A@Oy`SZ_IYR((AHW{03+NV@Z%-FoVOh8)+a; zlDE5yQ1zZ1Ae(`+z$3C4NPB>>+sSM@AS2(?#W6(Ua%g{~P=f-Gv+27P9V>QJ|4;U6 zbe!GKH+kPSMNh%0YD;wVPp!&p$=H2Qa>4_iC-Ws`4oVu`kXvDIe5l_eV|jwW;+5+S zYgy6~PYdy+-cS45)~k2Z!$WAAUct+ok^krC+MlVs+ppfyc`@F`nC<>l-VMq?t5r)} zBT7;dOH!?pi&B9UgOP!up{{|Eu910&p^25DrIo3vwt<<IfkFPhkew(Ra`RI%(<*Um UkbC@w6{vy1)78&qol`;+0LQMW5dZ)H literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/materialized_view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/materialized_view.png new file mode 100644 index 0000000000000000000000000000000000000000..7b72bdbb024d206a9ccccc307b980af7dc9cea88 GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wk0|O(M zr;B5V#p&b(iHrjr$vHbE{{BC@z1bzxXo_Zv!8F~plmsAX<lOhGZpy*4Te=!QPFFbm zU;Yt~PrZwz#Q!2=gMZH(vj3d_(fm;OaAw=?^=1c;K4AOv`+xgG`_JKRvEsKs+|pZn z;^>D*x6OT+Cpd08n!IM#^LZ1F3M3y)72cH;)3)hB@w(<$ENtuI!+37X&3YKpe7)iO zo7EB_E>059WTrQES9;xXdidD%!K10kJRqPs&0k{Df9H%vu?(M!xtNs?&v|Ub)6*j$ zbyq+v$yxg5%?jQE!}Cc$KEG!_e82v_q}|_|69*1#=yHs8ZfyjDhri#K1C8<Dp#F4o lfA#<Q^_TpMg5)ZIft4K|E4KUpCSXu8c)I$ztaD0e0sxS)lm-9* literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/img/view.png new file mode 100644 index 0000000000000000000000000000000000000000..44116db56496bb477c8ece7d3fdda666e1b3758b GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE08|>xOdKRnaBToAO4>7 z;On9XZ+AU@dGYD<`_G?0efI3x<EOW7JYRF{Me@Q^nvec>Z-1%w|NsB(FVt^;p7P|y zjoU9bT=|i6;cMdQ&tb<tdSCgTbKzU!@sHlyU#LC&-+lap_k(|JcmCAf{8f7ONA}_O z4*TAi?Rus6RdrP&&|=1tAirP+hi5m^K%69RcNf7&TWxm%Ih+L^k;OpT1B~5HX4?T7 zS)MMAArhC96Am!+_;@g=95{03(5YkR4xT)E^z7l&>_QLRH3SlxI5?abofwq#PpGhn z3y3`waupO1o<6}vaKfZ%6BvUwB`{h}XxPzW6T>ijp+R<{)&<#R3sx-A(NkQ!Xcb#n z<0DHqpYzNNJzLm||5$yx0JK%L#5JNMC9x#cD!C{XNHG{07#ipr8tEDsh8P)GnHXD{ zm}(oCS{WG3d#ASwMMG|WN@iLmM1z4PSOZYCiIssFM8m1+p=*E|7(8A5T-G@yGywnx Cm9(({ literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css new file mode 100644 index 0000000..80b64ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/css/materialized_view.css @@ -0,0 +1,12 @@ +.icon-materialized_view{ + background-image: url('{{ url_for('NODE-materialized_view.static', filename='img/materialized_view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.sql_field_height_280 { + height: 280px; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js new file mode 100644 index 0000000..2518a7d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/js/materialized_view.js @@ -0,0 +1,269 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-materialized_view']) { + var materialized_views= pgAdmin.Browser.Nodes['coll-materialized_view'] = + pgAdmin.Browser.Collection.extend({ + node: 'materialized_view', + label: '{{ _("Materialized Views") }}', + type: 'coll-materialized_view', + columns: ['name', 'owner'] + }); + } + + /** + Create and Add a View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['materialized_view']) { + pgAdmin.Browser.Nodes['materialized_view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'materialized_view', + label: '{{ _("Materialized View") }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-materialized_view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-materialized_view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_materialized_view_on_coll', node: 'coll-materialized_view', + module: this, applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_materialized_view', node: 'materialized_view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_materialized_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("Materialized View...") }}', + icon: 'wcTabIcon icon-materialized_view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _("Name") }}', cell: 'string', + type: 'text' + }, + { + id: 'oid', label:'{{ _("OID") }}', cell: 'string', + type: 'text', disabled: true, mode: ['properties'] + }, + { + id: 'owner', label:'{{ _("Owner") }}', cell: 'string', + control: 'node-list-by-name', node: 'role' + }, + { + id: 'schema', label:'{{ _("Schema") }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _("Comment") }}', cell: 'string', + type: 'multiline' + }, + { + id: 'system_view', label:'{{ _("System view?") }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'], + }, + { + id: 'definition', label:'{{ _("") }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], group: 'Definition', + control: Backform.SqlFieldControl, extraClasses:['sql_field_height_280'] + }, + // Add Privilege Control + { + id: 'datacl', label: '{{ _("Privileges") }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: '{{ _("Security") }}', + mode: ['edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', priority: 3 + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _("Security Labels") }}', + model: Backform.SecurityModel, editable: false, type: 'collection', + canEdit: false, group: '{{ _("Security") }}', canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', uniqueCol : ['provider'] + }, + { + id: 'with_data', label: '{{ _("With Data") }}', + group: '{{ _("Storage") }}', mode: ['edit', 'create'], + type: 'switch', + }, + { + id: 'spcname', label: '{{ _("Tablespace") }}', + editable: false, type: 'text', group: '{{ _("Storage") }}', + control: 'node-list-by-name', node: 'tablespace', + filter: function(m) { + if (m.label == "pg_global") return false; + else return true; + } + }, + { + id: 'fillfactor', label: '{{ _("Fill Factor") }}', + group: '{{ _("Storage") }}', mode: ['edit', 'create'], + type: 'integer', min: 10, max: 100 + }, + { + type: 'nested', control: 'tab', id: 'materialization', + label: '{{ _("Materialization") }}', mode: ['edit', 'create'], + group: '{{ _("Storage") }}', + schema: Backform.VacuumSettingsSchema + }, + { + id: 'acl', label: '{{ _("ACL") }}', + mode: ['properties'], type: 'text' + }, + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + }, + // We will disable everything if we are under catalog node + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) + { + return true; + } + return false; + } + + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check === false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-materialized_view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-materialized_view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..0c1bd6c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/acl.sql @@ -0,0 +1,41 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, + d.grantor, + d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'm' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql new file mode 100644 index 0000000..03260c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/create.sql @@ -0,0 +1,45 @@ +{# ===================== Create new view ===================== #} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if(data.fillfactor or data['autovacuum_enabled'] or data['toast_autovacuum_enabled']) %} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['autovacuum_enabled'] or data['toast_autovacuum_enabled'] or data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['autovacuum_enabled'] %} + autovacuum_enabled = {{ data['autovacuum_enabled']|lower }}{% if data['toast_autovacuum_enabled'] or data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['toast_autovacuum_enabled'] %} + {{ 'toast.autovacuum_enabled' }} = {{ data['toast_autovacuum_enabled']|lower }}{% if data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% for field in data['vacuum_data'] %} +{% if field.value is defined and field.value != '' and field.value != none %} +{% if loop.index > 1%}, +{% endif %} {{ field.name }} = {{ field.value|lower }}{% endif %} +{% endfor %}{{ '\r' }} +) +{% endif %} +{% if data.spcname %}TABLESPACE {{ data.spcname }} +{% endif %} AS +{{ data.definition.rstrip(';') }} +{% if data.with_data %} + WITH DATA; +{% else %} + WITH NO DATA; +{% endif %} +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..cf667fc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# =================== Drop/Cascade materialized view by name ====================#} +{% if vid %} +SELECT + c.relname As name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql new file mode 100644 index 0000000..a8a482c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/get_tblspc.sql @@ -0,0 +1,2 @@ +{# ===== Get list of tablespaces ===== #} +select tblspc.spcname from pg_tablespace tblspc WHERE tblspc.spcname != 'pg_global' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..6cdc8e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ==== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..f02bc54 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/properties.sql @@ -0,0 +1,111 @@ +{# ========================== Fetch Materialized View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + c.relispopulated AS with_data, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relacl, + nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, + description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + array_to_string(c.relacl::text[], ', ') AS acl, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels, + substring(array_to_string(c.reloptions, ',') + FROM 'fillfactor=([0-9]*)') AS fillfactor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + c.reloptions AS reloptions, tst.reloptions AS toast_reloptions, + (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, + (CASE WHEN (substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean THEN true ELSE false END) AS autovacuum_custom, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean AND c.reltoastrelid = 0 THEN true ELSE false END) AS toast_autovaccum +FROM + pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'm'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql new file mode 100644 index 0000000..30a9a34 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/update.sql @@ -0,0 +1,152 @@ +{# ===================== Update View ===================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{%- if data -%} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{# ===== Rename mat view ===== #} +{% if data.name and data.name != o_data.name %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{# ===== Alter schema view ===== #} +{% if data.schema and data.schema != o_data.schema %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; + +{% endif %} +{# ===== Alter Table owner ===== #} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; + +{% endif %} +{% if data['vacuum_data']['reset']|length > 0 %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( +{% for field in data['vacuum_data']['reset'] %} + {{ field.name }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +) +{% endif %} +{# ===== First Drop and then create mat view ===== #} +{% if def and def != o_data.definition.rstrip(';') %} +{% if data.fillfactor or ( data['vacuum_data']['changed']|length > 0 ) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }}; +CREATE MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']['changed']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['vacuum_data']['changed']|length > 0 %} +{% for field in data['vacuum_data']['changed'] %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +{% endif %} +) + AS +{{ def }} +{% if data.with_data %} + WITH DATA; + +{% else %} + WITH NO DATA; + +{% endif %} +{% endif %} +{% else %} +{# ======= Alter Tablespace ========= #} +{%- if data.spcoid and o_data.spcoid != data.spcoid -%} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET TABLESPACE {{ data.spcoid }}; + +{% endif %} +{# ======= SET/RESET Fillfactor ========= #} +{% if data.fillfactor and o_data.fillfactor != data.fillfactor %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +SET(FILLFACTOR = {{ data.fillfactor }}); + +{% elif data.fillfactor == '' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + FILLFACTOR +); + +{% endif %} +{# ===== Check for with_data property ===== #} +{% if data.with_data is defined and o_data.with_data|lower != data.with_data|lower %} +REFRESH MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} WITH{{ ' NO' if data.with_data|lower == 'false' else '' }} DATA; + +{% endif %} +{# ===== Check for Table tab properties ===== #} +{% if data.autovacuum_custom is defined and data.autovacuum_custom|lower == 'false' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + autovacuum_vacuum_threshold, + autovacuum_analyze_threshold, + autovacuum_vacuum_scale_factor, + autovacuum_analyze_scale_factor, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_freeze_min_age, + autovacuum_freeze_max_age, + autovacuum_freeze_table_age +); + +{% elif(data['vacuum_data']['changed']|length > 0) %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} SET( +{% for field in data['vacuum_data']['changed'] %} +{% if field.value != None %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endif %} +{% endfor %} +); + +{% endif %}{# ===== End check for custom autovaccum ===== #} +{% endif %}{# ===== End block for check data definition ===== #} +{%- if data.comment and data.comment != o_data.comment -%} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; + +{% endif %} +{# ============= The SQL generated below will change privileges ============= #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed -%} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{%- endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# ============== The SQL generated below will change Security Label ========= #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..d192a80 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/pg/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ==== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..0c1bd6c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/acl.sql @@ -0,0 +1,41 @@ +{#============================Get ACLs=========================#} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, + d.grantor, + d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'm' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql new file mode 100644 index 0000000..d5f1a87 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/create.sql @@ -0,0 +1,39 @@ +{# ===================== Create new view ===================== #} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if(data.fillfactor or data['autovacuum_enabled'] or data['toast_autovacuum_enabled']) %} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% for field in data['vacuum_data'] %} +{% if field.value is defined and field.value != '' and field.value != none %} +{% if loop.index > 1%}, +{% endif %} {{ field.name }} = {{ field.value|lower }}{% endif %} +{% endfor %}{{ '\r' }} +) +{% endif %} +{% if data.spcname %}TABLESPACE {{ data.spcname }} +{% endif %} AS +{{ data.definition.rstrip(';') }} +{% if data.with_data %} + WITH DATA; +{% else %} + WITH NO DATA; +{% endif %} +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..cf667fc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# =================== Drop/Cascade materialized view by name ====================#} +{% if vid %} +SELECT + c.relname As name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(nspname, name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql new file mode 100644 index 0000000..a8a482c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/get_tblspc.sql @@ -0,0 +1,2 @@ +{# ===== Get list of tablespaces ===== #} +select tblspc.spcname from pg_tablespace tblspc WHERE tblspc.spcname != 'pg_global' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..6cdc8e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ==== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# We will generate Security Label SQL using macro #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..f02bc54 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/properties.sql @@ -0,0 +1,111 @@ +{# ========================== Fetch Materialized View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + c.relispopulated AS with_data, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relacl, + nsp.nspname as schema, + pg_get_userbyid(c.relowner) AS owner, + description AS comment, + pg_get_viewdef(c.oid, true) AS definition, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + array_to_string(c.relacl::text[], ', ') AS acl, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid AND sl1.objsubid=0) AS seclabels, + substring(array_to_string(c.reloptions, ',') + FROM 'fillfactor=([0-9]*)') AS fillfactor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + c.reloptions AS reloptions, tst.reloptions AS toast_reloptions, + (CASE WHEN c.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, + (CASE WHEN (substring(array_to_string(c.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean THEN true ELSE false END) AS autovacuum_custom, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') + FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean AND c.reltoastrelid = 0 THEN true ELSE false END) AS toast_autovaccum +FROM + pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'm'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql new file mode 100644 index 0000000..df4221d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/update.sql @@ -0,0 +1,144 @@ +{# ===================== Update View ===================#} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{%- if data -%} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{# ===== Rename mat view ===== #} +{% if data.name and data.name != o_data.name %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{# ===== Alter schema view ===== #} +{% if data.schema and data.schema != o_data.schema %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; + +{% endif %} +{# ===== Alter Table owner ===== #} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; + +{% endif %} +{# ===== First Drop and then create mat view ===== #} +{% if def and def != o_data.definition.rstrip(';') %} +{% if data.fillfactor or (data['autovacuum_enabled'] and data['vacuum_data']['changed']|length > 0) %} +DROP MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }}; +CREATE MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +WITH( +{% if data.fillfactor %} + FILLFACTOR = {{ data.fillfactor }}{% if data['vacuum_data']['changed']|length > 0 %},{% endif %}{{ '\r' }} +{% endif %} +{% if data['vacuum_data']['changed']|length > 0 %} +{% for field in data['vacuum_data']['changed'] %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endfor %} +{% endif %} +) + AS +{{ def }} +{% if data.with_data %} + WITH DATA; + +{% else %} + WITH NO DATA; + +{% endif %} +{% endif %} +{% else %} +{# ======= Alter Tablespace ========= #} +{%- if data.spcoid and o_data.spcoid != data.spcoid -%} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET TABLESPACE {{ data.spcoid }}; + +{% endif %} +{# ======= SET/RESET Fillfactor ========= #} +{% if data.fillfactor and o_data.fillfactor != data.fillfactor %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +SET(FILLFACTOR = {{ data.fillfactor }}); + +{% elif data.fillfactor == '' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + FILLFACTOR +); + +{% endif %} +{# ===== Check for with_data property ===== #} +{% if data.with_data is defined and o_data.with_data|lower != data.with_data|lower %} +REFRESH MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} WITH{{ ' NO' if data.with_data|lower == 'false' else '' }} DATA; + +{% endif %} +{# ===== Check for Table tab properties ===== #} +{% if data.autovacuum_custom is defined and data.autovacuum_custom|lower == 'false' %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(view_schema, view_name) }} +RESET( + autovacuum_vacuum_threshold, + autovacuum_analyze_threshold, + autovacuum_vacuum_scale_factor, + autovacuum_analyze_scale_factor, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_freeze_min_age, + autovacuum_freeze_max_age, + autovacuum_freeze_table_age +); + +{% elif(data['vacuum_data']['changed']|length > 0) %} +ALTER MATERIALIZED VIEW {{ conn|qtIdent(data.schema, data.name) }} SET( +{% for field in data['vacuum_data']['changed'] %} +{% if field.value != None %} + {{ field.name }} = {{ field.value|lower }}{% if not loop.last %},{% endif %}{{ '\r' }} +{% endif %} +{% endfor %} +); + +{% endif %}{# ===== End check for custom autovaccum ===== #} +{% endif %}{# ===== End block for check data definition ===== #} +{%- if data.comment and data.comment != o_data.comment -%} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; + +{% endif %} +{# ============= The SQL generated below will change privileges ============= #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed -%} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{%- endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# ============== The SQL generated below will change Security Label ========= #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..d192a80 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/materialized_view/ppas/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ==== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css new file mode 100644 index 0000000..89eae4e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/css/view.css @@ -0,0 +1,12 @@ +.icon-view{ + background-image: url('{{ url_for('NODE-view.static', filename='img/view.png') }}') !important; + border-radius: 10px; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.sql_field_height_140 { + height: 140px; +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js new file mode 100644 index 0000000..364b6a7 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/js/view.js @@ -0,0 +1,274 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'codemirror', 'pgadmin.browser.server.privilege'], + +function($, _, S, pgAdmin, pgBrowser, CodeMirror) { + + /** + Create and add a view collection into nodes + @param {variable} label - Label for Node + @param {variable} type - Type of Node + @param {variable} columns - List of columns to + display under under properties. + */ + if (!pgBrowser.Nodes['coll-view']) { + var views= pgAdmin.Browser.Nodes['coll-view'] = + pgAdmin.Browser.Collection.extend({ + node: 'view', + label: '{{ _("Views") }}', + type: 'coll-view', + columns: ["name", "owner"] + }); + } + + /** + Create and Add a View Node into nodes + @param {variable} parent_type - The list of nodes + under which this node to display + @param {variable} type - Type of Node + @param {variable} hasSQL - To show SQL tab + @param {variable} canDrop - Adds drop view option + in the context menu + @param {variable} canDropCascade - Adds drop Cascade + view option in the context menu + */ + if (!pgBrowser.Nodes['view']) { + pgAdmin.Browser.Nodes['view'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'view', + label: '{{ _("View") }}', + hasSQL: true, + hasDepends: true, + collection_type: 'coll-view', + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + Init: function() { + + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + /** + Add "create view" menu option into context and object menu + for the following nodes: + coll-view, view and schema. + @property {data} - Allow create view option on schema node or + system view nodes. + */ + pgBrowser.add_menus([{ + name: 'create_view_on_coll', node: 'coll-view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("View...") }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("View...") }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_view', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _("View...") }}', + icon: 'wcTabIcon icon-view', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + }, + + /** + Define model for the view node and specify the + properties of the model in schema. + */ + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + }, + schema: [{ + id: 'name', label: '{{ _("Name") }}', cell: 'string', + type: 'text', disabled: 'inSchema' + }, + { + id: 'oid', label:'{{ _("OID") }}', cell: 'string', + type: 'text', disabled: true, mode: ['properties'] + }, + { + id: 'owner', label:'{{ _("Owner") }}', cell: 'string', control: 'node-list-by-name', + node: 'role', disabled: 'inSchema' + }, + { + id: 'schema', label:'{{ _("Schema") }}', cell: 'string', first_empty: false, + type: 'options', node: 'schema', disabled: 'inSchema', + control: Backform.NodeListByNameControl.extend({ + render: function(){ + + // Initialize parent's render method + Backform.NodeListByNameControl.prototype.render.apply(this, arguments); + + // Set schema default value to its parent Schema + if(this.model.isNew()){ + this.model.set({'schema': this.model.node_info.schema.label}); + } + return this; + } + }), + + // Don't show schema starts with "pg_" + filter: function(d){ + if(d.label && d.label.match(/^pg_/)){ + return false; + } + return true; + } + }, + { + id: 'comment', label:'{{ _("Comment") }}', cell: 'string', + type: 'multiline', disabled: 'inSchema' + }, + { + id: 'system_view', label:'{{ _("System view?") }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'] + }, + { + id: 'security_barrier', label:'{{ _("Security Barrier") }}', cell: 'string', + type: 'switch', min_version: '90200', mode: ['create', 'edit'], + group: 'Definition', disabled: 'inSchema' + }, + { + id: 'check_option', label:'{{ _("Check Options") }}', + control: 'select2', group: 'Definition', type: 'text', + min_version: '90400', mode:['properties', 'create', 'edit'], + select2: { + + // set select2 option width to 100% + width: '100%' + }, + options:[ + {label: "No", value: "no"}, + {label: "Local", value: "local"}, + {label: "Cascaded", value: "cascaded"} + ], disabled: 'inSchema' + }, + { + id: 'definition', label:'{{ _("Definition") }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], group: 'Definition', + control: Backform.SqlFieldControl, extraClasses:['sql_field_height_140'], + disabled: 'inSchema' + }, + { + id: 'acl', label: '{{ _("ACL") }}', + mode: ['properties'], type: 'text' + },{ + id: 'security', label: '{{ _("Security") }}', + type: 'group', + visible: function(m) { + if (m.top && 'catalog' in m.top.node_info) { + return false; + } + return true; + } + }, + + // Add Privilege Control + { + id: 'datacl', label: '{{ _("Privileges") }}', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend( + {privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), uniqueCol : ['grantee'], + editable: false, type: 'collection', group: 'security', + mode: ['edit', 'create'], canAdd: true, canDelete: true, + control: 'unique-col-collection', disabled: 'inSchema' + }, + + // Add Security Labels Control + { + id: 'seclabels', label: '{{ _("Security Labels") }}', + model: Backform.SecurityModel, editable: false, type: 'collection', + canEdit: false, group: 'security', canDelete: true, + mode: ['edit', 'create'], canAdd: true, disabled: 'inSchema', + control: 'unique-col-collection', uniqueCol : ['provider'] + }, + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'), + field_def = this.get('definition'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') == '') { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + }else{ + this.errorModel.unset('name'); + } + if(this.isNew()){ + if (_.isUndefined(field_def) || _.isNull(field_def) || + String(field_def).replace(/^\s+|\s+$/g, '') == '') { + err['definition'] = '{{ _("Please enter function definition.") }}'; + errmsg = errmsg || err['definition']; + this.errorModel.set('definition', errmsg); + return errmsg; + }else{ + this.errorModel.unset('definition'); + } + } + return null; + }, + // We will disable everything if we are under catalog node + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) + { + return true; + } + return false; + } + }), + + /** + Show or hide create view menu option on parent node + and hide for system view in catalogs. + */ + canCreate: function(itemData, item, data) { + + // If check is false then, we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create view + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-view' == d._type) { + + // Check if we are not child of view + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // by default we do not want to allow create menu + return true; + + } + }); + } + + return pgBrowser.Nodes['coll-view']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql new file mode 100644 index 0000000..5850639 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/acl.sql @@ -0,0 +1,51 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT + (d).grantee AS grantee, + (d).grantor AS grantor, + (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM + (SELECT + aclexplode(relacl) AS d + FROM + pg_class cl1 + WHERE + cl1.oid = {{ vid }} + ) a + ) d + ) d + LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) + LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql new file mode 100644 index 0000000..d54fff9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/create.sql @@ -0,0 +1,21 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql new file mode 100644 index 0000000..ce1d801 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/properties.sql @@ -0,0 +1,69 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + nsp.nspname AS schema, + description AS comment, + c.reltablespace AS spcoid, + pg_get_userbyid(c.relowner) AS owner, + pg_get_viewdef(c.oid, true) AS definition, + array_to_string(c.relacl::text[], ', ') AS acl, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} + (SELECT + array_agg(label) + FROM + pg_seclabels sl1 + WHERE + sl1.objoid=c.oid AND sl1.objsubid=0 + ) AS labels, + (SELECT + array_agg(provider) + FROM + pg_seclabels sl2 + WHERE sl2.objoid=c.oid AND sl2.objsubid=0 + ) AS providers +FROM pg_class c + LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace + LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +WHERE ((c.relhasrules + AND + (EXISTS( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) AND (bpchar(r.ev_type) = '1'::bpchar)) + )) + ) AND (c.relkind = 'v'::char)) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql new file mode 100644 index 0000000..2f7f82e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/update.sql @@ -0,0 +1,66 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} + AS{{ def }}; +{% endif %} +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.1_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql new file mode 100644 index 0000000..5f2d0fb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/acl.sql @@ -0,0 +1,52 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT + (d).grantee AS grantee, + (d).grantor AS grantor, + (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM + (SELECT + aclexplode(relacl) AS d + FROM + pg_class cl1 + WHERE + cl1.oid = {{ vid }} + ) a + ) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql new file mode 100644 index 0000000..eebe0f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/properties.sql @@ -0,0 +1,70 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + pg_get_userbyid(c.relowner) AS owner, + description As comment, + pg_get_viewdef(c.oid, true) AS definition, + nsp.nspname AS schema, + array_to_string(c.relacl::text[], ', ') AS acl, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar) + ) )) + ) AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql new file mode 100644 index 0000000..c37d4ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/update.sql @@ -0,0 +1,74 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) {% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.2_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..84300cf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/acl.sql @@ -0,0 +1,38 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..5d3d1e4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/properties.sql @@ -0,0 +1,74 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + nsp.nspname AS schema, + c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql new file mode 100644 index 0000000..02d8f37 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/update.sql @@ -0,0 +1,75 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql new file mode 100644 index 0000000..7d791f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/acl.sql @@ -0,0 +1,39 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql new file mode 100644 index 0000000..191090d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier }} +{% if data.security_barrier %} security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner and data.m_view is undefined %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql new file mode 100644 index 0000000..c7380d8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/properties.sql @@ -0,0 +1,77 @@ +{# ========================== Fetch View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + c.reltablespace AS spcoid, + nsp.nspname AS schema, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'check_option=([a-z]*)') AS check_option, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql new file mode 100644 index 0000000..56183fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/update.sql @@ -0,0 +1,82 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql new file mode 100644 index 0000000..ef27594 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/pg/9.4_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ===== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql new file mode 100644 index 0000000..7d791f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/acl.sql @@ -0,0 +1,39 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql new file mode 100644 index 0000000..191090d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier }} +{% if data.security_barrier %} security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner and data.m_view is undefined %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql new file mode 100644 index 0000000..2af1252 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/properties.sql @@ -0,0 +1,76 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + c.reltablespace AS spcoid, + nsp.nspname AS schema, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'check_option=([a-z]*)') AS check_option, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql new file mode 100644 index 0000000..56183fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/update.sql @@ -0,0 +1,82 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql new file mode 100644 index 0000000..ef27594 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.1_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ===== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/acl.sql new file mode 100644 index 0000000..5f2d0fb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/acl.sql @@ -0,0 +1,52 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + (SELECT + (d).grantee AS grantee, + (d).grantor AS grantor, + (d).is_grantable AS is_grantable, + (d).privilege_type AS privilege_type + FROM + (SELECT + aclexplode(relacl) AS d + FROM + pg_class cl1 + WHERE + cl1.oid = {{ vid }} + ) a + ) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/properties.sql new file mode 100644 index 0000000..eebe0f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/properties.sql @@ -0,0 +1,70 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relname AS name, + c.reltablespace AS spcoid, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + pg_get_userbyid(c.relowner) AS owner, + description As comment, + pg_get_viewdef(c.oid, true) AS definition, + nsp.nspname AS schema, + array_to_string(c.relacl::text[], ', ') AS acl, + {#=============Checks if it is system view================#} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers + , substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar) + ) )) + ) AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid ORDER BY c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/update.sql new file mode 100644 index 0000000..c37d4ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/update.sql @@ -0,0 +1,74 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) {% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.2_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/acl.sql new file mode 100644 index 0000000..84300cf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/acl.sql @@ -0,0 +1,38 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON ( + cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/create.sql new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/create.sql @@ -0,0 +1,22 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if (data.security_barrier) %} +WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ data.owner }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/properties.sql new file mode 100644 index 0000000..5d3d1e4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/properties.sql @@ -0,0 +1,74 @@ +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + nsp.nspname AS schema, + c.reltablespace AS spcoid, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/update.sql new file mode 100644 index 0000000..02d8f37 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/update.sql @@ -0,0 +1,75 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/view_id.sql new file mode 100644 index 0000000..6dff04e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.3_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# Below will provide view id for newly created view #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/acl.sql new file mode 100644 index 0000000..7d791f5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/acl.sql @@ -0,0 +1,39 @@ +{# ============================ Get ACLs ========================= #} +{% if vid %} +SELECT + 'datacl' as deftype, + COALESCE(gt.rolname, 'public') grantee, + g.rolname grantor, + array_agg(privilege_type) as privileges, + array_agg(is_grantable) as grantable +FROM + (SELECT + d.grantee, d.grantor, d.is_grantable, + CASE d.privilege_type + WHEN 'DELETE' THEN 'd' + WHEN 'INSERT' THEN 'a' + WHEN 'REFERENCES' THEN 'x' + WHEN 'SELECT' THEN 'r' + WHEN 'TRIGGER' THEN 't' + WHEN 'UPDATE' THEN 'w' + WHEN 'TRUNCATE' THEN 'D' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT + relacl + FROM + pg_class cl + LEFT OUTER JOIN pg_shdescription descr ON + (cl.oid=descr.objoid AND descr.classoid='pg_class'::regclass) + WHERE + cl.oid = {{ vid }}::OID AND relkind = 'v' + ) acl, + aclexplode(relacl) d + ) d +LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid) +LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid) +GROUP BY + g.rolname, + gt.rolname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/create.sql new file mode 100644 index 0000000..191090d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/create.sql @@ -0,0 +1,25 @@ +{#============================Create new view=========================#} +{% if display_comments %} +-- View: {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP VIEW {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.definition %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(data.schema, data.name) }} +{% if ((data.check_option and data.check_option.lower() != 'no') or data.security_barrier) %} +WITH ({% if data.check_option and data.check_option.lower() != 'no' %} +check_option={{ data.check_option }}{% endif %}{{ ', ' if data.check_option and +data.check_option.lower() != 'no' and data.security_barrier }} +{% if data.security_barrier %} security_barrier={{ data.security_barrier|lower }}{% endif %}){% endif %} AS +{{ data.definition.rstrip(';') }}; +{% if data.owner and data.m_view is undefined %} + +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/delete.sql new file mode 100644 index 0000000..e50beec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/delete.sql @@ -0,0 +1,13 @@ +{# ====================== Drop/Cascade view by name ===================== #} +{% if vid %} +SELECT + c.relname AS name, + nsp.nspname +FROM + pg_class c +LEFT JOIN pg_namespace nsp ON c.relnamespace = nsp.oid +WHERE + c.relfilenode = {{ vid }}; +{% elif (name and nspname) %} +DROP VIEW {{nspname}}.{{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/grant.sql new file mode 100644 index 0000000..ce94ecc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/grant.sql @@ -0,0 +1,14 @@ +{# ===== Grant Permissions to User Role on Views/Tables ===== #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{# ===== We will generate Security Label SQL using macro ===== #} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if data.datacl %} +{% for priv in data.datacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/properties.sql new file mode 100644 index 0000000..c7380d8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/properties.sql @@ -0,0 +1,77 @@ +{# ========================== Fetch View Properties ========================= #} +{% if (vid and datlastsysoid) or scid %} +SELECT + c.oid, + c.xmin, + c.relkind, + description, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + c.relname AS name, + c.reltablespace AS spcoid, + nsp.nspname AS schema, + c.relispopulated AS ispopulated, + pg_get_userbyid(c.relowner) AS owner, + array_to_string(c.relacl::text[], ', ') AS acl, + pg_get_viewdef(c.oid, true) AS definition, + {# ===== Checks if it is system view ===== #} + {% if vid and datlastsysoid %} + CASE WHEN {{vid}} <= {{datlastsysoid}} THEN True ELSE False END AS system_view, + {% endif %} +(SELECT + array_agg(label) +FROM + pg_seclabels sl1 +WHERE + sl1.objoid=c.oid AND sl1.objsubid=0) AS labels, +(SELECT + array_agg(provider) +FROM + pg_seclabels sl2 +WHERE + sl2.objoid=c.oid AND sl2.objsubid=0) AS providers, + substring(array_to_string(c.reloptions, ',') + FROM 'check_option=([a-z]*)') AS check_option, + substring(array_to_string(c.reloptions, ',') + FROM 'security_barrier=([a-z|0-9]*)') AS security_barrier +FROM pg_class c +LEFT OUTER JOIN pg_namespace nsp on nsp.oid = c.relnamespace +LEFT OUTER JOIN pg_tablespace spc on spc.oid=c.reltablespace +LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid + WHERE ((c.relhasrules AND (EXISTS ( + SELECT + r.rulename + FROM + pg_rewrite r + WHERE + ((r.ev_class = c.oid) + AND (bpchar(r.ev_type) = '1'::bpchar)) ))) + AND (c.relkind = 'v'::char) + ) +{% if (vid and datlastsysoid) %} + AND c.oid = {{vid}}::oid +{% elif scid %} + AND c.relnamespace = {{scid}}::oid +ORDER BY + c.relname +{% endif %} + +{% elif type == 'roles' %} +SELECT + pr.rolname +FROM + pg_roles pr +WHERE + pr.rolcanlogin +ORDER BY + pr.rolname + +{% elif type == 'schemas' %} +SELECT + nsp.nspname +FROM + pg_namespace nsp +WHERE + (nsp.nspname NOT LIKE E'pg\\_%' + AND nsp.nspname != 'information_schema') +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/update.sql new file mode 100644 index 0000000..56183fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/update.sql @@ -0,0 +1,82 @@ +{# ============================ Update View ========================= #} +{% import 'macros/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{% set view_name = data.name if data.name else o_data.name %} +{% set view_schema = data.schema if data.schema else o_data.schema %} +{% set def = data.definition.rstrip(';') if data.definition %} +{% if data.name and data.name != o_data.name %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.schema and data.schema != o_data.schema %} +ALTER VIEW {{ conn|qtIdent(o_data.schema, view_name ) }} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; +{% endif %} +{% if data.owner and data.owner != o_data.owner %} +ALTER TABLE {{ conn|qtIdent(view_schema, view_name) }} + OWNER TO {{ conn|qtIdent(data.owner) }}; +{% endif %} +{% if def and def != o_data.definition.rstrip(';') %} +CREATE OR REPLACE VIEW {{ conn|qtIdent(view_schema, view_name) }} +{% if (data.check_option and data.check_option != o_data.check_option) + or (data.security_barrier and data.security_barrier != o_data.security_barrier) %} + WITH ({% if (data.check_option and data.check_option != 'no') %}check_option={{ data.check_option }}{% endif %}{{', ' if data.check_option and data.check_option != 'no' and data.security_barrier }}{% if data.security_barrier %}security_barrier={{ data.security_barrier|lower }}{% endif %}) +{% endif %} + AS{{ def }}; +{% else %} +{% if (data.security_barrier is defined and data.security_barrier|lower != o_data.security_barrier|lower) %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (security_barrier={{ data.security_barrier|lower }}); +{% endif %} +{% if (data.check_option and data.check_option != o_data.check_option and data.check_option != 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} + SET (check_option={{ data.check_option }}); +{% elif (data.check_option and data.check_option != o_data.check_option and data.check_option == 'no') %} +ALTER VIEW {{ conn|qtIdent(view_schema, view_name) }} RESET (check_option); +{% endif %} +{% endif %} + +{% if data.comment and data.comment != o_data.comment %} +COMMENT ON VIEW {{ conn|qtIdent(view_schema, view_name) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} +{# The SQL generated below will change privileges #} +{% if data.datacl %} +{% if 'deleted' in data.datacl %} +{% for priv in data.datacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.datacl %} +{% for priv in data.datacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.datacl %} +{% for priv in data.datacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{# The SQL generated below will change Security Label #} +{% if data.seclabels is not none and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'VIEW', data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'VIEW', data.name, r.provider, r.security_label) }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/view_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/view_id.sql new file mode 100644 index 0000000..ef27594 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/view/ppas/9.4_plus/sql/view_id.sql @@ -0,0 +1,4 @@ +{# ===== Below will provide view id for last created view ===== #} +{% if data %} +SELECT c.oid FROM pg_class c WHERE c.relname = '{{ data.name }}'; +{% endif %} ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2016-04-11 11:49 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2016-02-02 08:29 [pgAdmin4] PATCH: View and Role Node Surinder Kumar <[email protected]> 2016-02-24 13:31 ` Surinder Kumar <[email protected]> 2016-02-25 11:03 ` Dave Page <[email protected]> 2016-03-01 05:16 ` Surinder Kumar <[email protected]> 2016-03-01 17:12 ` Dave Page <[email protected]> 2016-03-23 15:44 ` Surinder Kumar <[email protected]> 2016-04-11 11:49 ` Surinder Kumar <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox