From ca277bc8b67d6fe7ad3d0c56bc5cec510835226e Mon Sep 17 00:00:00 2001 From: Murtuza Zabuawala Date: Thu, 7 Apr 2016 12:27:33 +0100 Subject: [PATCH] Type support --- .../databases/schemas/types/__init__.py | 1210 +++++++++++++++++ .../schemas/types/static/img/coll-type.png | Bin 0 -> 329 bytes .../schemas/types/static/img/type.png | Bin 0 -> 325 bytes .../schemas/types/templates/type/js/type.js | 863 ++++++++++++ .../types/templates/type/sql/9.1_plus/acl.sql | 26 + .../sql/9.1_plus/additional_properties.sql | 35 + .../templates/type/sql/9.1_plus/create.sql | 79 ++ .../templates/type/sql/9.1_plus/delete.sql | 1 + .../type/sql/9.1_plus/get_collations.sql | 7 + .../sql/9.1_plus/get_external_functions.sql | 40 + .../templates/type/sql/9.1_plus/get_oid.sql | 11 + .../type/sql/9.1_plus/get_subtypes.sql | 56 + .../templates/type/sql/9.1_plus/get_types.sql | 10 + .../templates/type/sql/9.1_plus/nodes.sql | 10 + .../type/sql/9.1_plus/properties.sql | 24 + .../templates/type/sql/9.1_plus/update.sql | 139 ++ .../type/sql/postgres_inbuit_types.txt | 53 + 17 files changed, 2564 insertions(+) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/coll-type.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/type.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/js/type.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/acl.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/additional_properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_collations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_external_functions.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_subtypes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_types.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/postgres_inbuit_types.txt diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py new file mode 100644 index 000000000..6a46bb0b0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py @@ -0,0 +1,1210 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Type 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.server_groups.servers.databases.schemas.utils \ + import SchemaChildModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \ + parse_priv_to_db +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps + + +class TypeModule(SchemaChildModule): + """ + class TypeModule(SchemaChildModule) + + A module class for Type node derived from SchemaChildModule + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Type and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - 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 type, when any of the server node is + initialized. + """ + + NODE_TYPE = 'type' + COLLECTION_LABEL = gettext("Types") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the TypeModule and it's base module. + + Args: + *args: + **kwargs: + """ + super(TypeModule, 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 database, when any of the database node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return False + +blueprint = TypeModule(__name__) + + +class TypeView(PGChildNodeView): + """ + This class is responsible for generating routes for Type node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the TypeView and it's base view. + + * check_precondition() + - 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 + + * list() + - This function is used to list all the Type nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Type node. + + * properties(gid, sid, did, scid, tid) + - This function will show the properties of the selected Type node + + * create(gid, sid, did, scid) + - This function will create the new Type object + + * update(gid, sid, did, scid, tid) + - This function will update the data for the selected Type node + + * delete(self, gid, sid, scid, tid): + - This function will drop the Type object + + * msql(gid, sid, did, scid, tid) + - This function is used to return modified SQL for the selected + Type node + + * get_sql(data, scid, tid) + - 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 + selected Type node. + + * dependency(gid, sid, did, scid, tid): + - This function will generate dependency list show it in dependency + pane for the selected Type node. + + * dependent(gid, sid, did, scid, tid): + - This function will generate dependent list to show it in dependent + pane for the selected Type node. + + * additional_properties(copy_dict, tid): + - This function will add additional properties in response + + * get_collations(gid, sid, did, scid, tid): + - This function will return list of collation in ajax response + + * get_types(gid, sid, did, scid, tid): + - This function will return list of types in ajax response + + * get_subtypes(gid, sid, did, scid, tid): + - This function will return list of subtypes in ajax response + + * get_subtype_opclass(gid, sid, did, scid, tid): + - This function will return list of subtype opclass in ajax response + + * get_subtype_diff(gid, sid, did, scid, tid): + - This function will return list of subtype diff functions + in ajax response + + * get_canonical(gid, sid, did, scid, tid): + - This function will return list of canonical functions + in ajax response + + * get_external_functions_list(gid, sid, did, scid, tid): + - This function will return list of external functions + in ajax response + """ + + 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': 'tid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + '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'}], + 'get_types': [{'get': 'get_types'}, {'get': 'get_types'}], + 'get_stypes': [{'get': 'get_subtypes'}, {'get': 'get_subtypes'}], + 'get_subopclass': [{'get': 'get_subtype_opclass'}, + {'get': 'get_subtype_opclass'}], + 'get_stypediff': [{'get': 'get_subtype_diff'}, {'get': 'get_subtype_diff'}], + 'get_canonical': [{'get': 'get_canonical'}, {'get': 'get_canonical'}], + 'get_collations': [{'get': 'get_collations'}, {'get': 'get_collations'}], + 'get_external_functions': [{'get': 'get_external_functions_list'}, + {'get': 'get_external_functions_list'}] + }) + + 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']) + + # We need datlastsysoid to check if current type is system type + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + + # 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!" + ) + ) + + # Declare allows acl on type + self.acl = ['U'] + + # we will set template path for sql scripts + self.template_path = 'type/sql/9.1_plus' + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid): + """ + This function is used to list all the type nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + + Returns: + JSON of available type nodes + """ + + SQL = render_template("/".join([self.template_path, 'properties.sql']), + scid=scid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects) + + 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): + """ + This function will used to create all the child node within that collection. + Here it will create all the type node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + + Returns: + JSON of available type child nodes + """ + + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), scid=scid, + show_system_objects=self.blueprint.show_system_objects) + 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-type" + )) + + return make_json_response( + data=res, + status=200 + ) + + def additional_properties(self, copy_dict, tid): + """ + We will use this function to add additional properties according to type + + Returns: + additional properties for type like range/composite/enum + + """ + # Fetching type of type + of_type = copy_dict['typtype'] + res = dict() + # If type is of Composite then we need to add members list in our output + if of_type == 'c': + SQL = render_template("/".join([self.template_path, + 'additional_properties.sql']), + type='c', + typrelid=copy_dict['typrelid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + # To display in properties + properties_list = [] + # To display in composite collection grid + composite_lst = [] + + for row in rset['rows']: + typelist = ' '.join([row['attname'], row['typname']]) + if not row['collname'] or (row['collname'] == 'default' + and row['collnspname'] == 'pg_catalog'): + full_collate = '' + collate = '' + else: + full_collate = get_driver(PG_DEFAULT_DRIVER).qtIdent( + self.conn, row['collnspname'], row['collname']) + collate = ' COLLATE ' + full_collate + typelist += collate + properties_list.append(typelist) + + # Below logic will allow us to split length, precision from type name for grid + import re + matchObj = re.match( r'(.*)\((.*?),(.*?)\)', row['typname']) + if matchObj: + t_name = matchObj.group(1) + t_len = matchObj.group(2) + t_prec = matchObj.group(3) + else: + t_name = row['typname'] + t_len = None + t_prec = None + + composite_lst.append({ + 'attnum':row['attnum'], 'member_name': row['attname'], 'type': t_name, 'collation': full_collate, + 'tlength': t_len, 'precision': t_prec }) + + # Adding both results + res['member_list'] = ', '.join(properties_list) + res['composite'] = composite_lst + + # If type is of ENUM then we need to add labels in our output + if of_type == 'e': + SQL = render_template("/".join([self.template_path, + 'additional_properties.sql']), + type='e', tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + # To display in properties + properties_list = [] + # To display in enum grid + enum_list = [] + for row in rset['rows']: + properties_list.append(row['enumlabel']) + enum_list.append({'label': row['enumlabel']}) + + # Adding both results in ouput + res['enum_list'] = ', '.join(properties_list) + res['enum'] = enum_list + + # If type is of Range then we need to add collation,subtype etc in our output + if of_type == 'r': + SQL = render_template("/".join([self.template_path, + 'additional_properties.sql']), + type='r', tid=tid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + range_dict = dict(res['rows'][0]) + res.update(range_dict) + + # Returning only additional properties only + return res + + @check_precondition + def properties(self, gid, sid, did, scid, tid): + """ + This function will show the properties of the selected type node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Type ID + + Returns: + JSON of selected type node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + copy_dict = dict(res['rows'][0]) + + # We need to parse & convert ACL coming from database to json format + SQL = render_template("/".join([self.template_path, 'acl.sql']), + scid=scid, tid=tid) + status, acl = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + copy_dict['typacl'] = [] + + + for row in acl['rows']: + priv = parse_priv_from_db(row) + if row['deftype'] in copy_dict: + copy_dict[row['deftype']].append(priv) + else: + copy_dict[row['deftype']] = [priv] + + # Calling function to check and additional properties if available + copy_dict.update(self.additional_properties(copy_dict, tid)) + + return ajax_response( + response=copy_dict, + status=200 + ) + + @check_precondition + def get_collations(self, gid, sid, did, scid, tid=None): + """ + This function will return list of collation available + as AJAX response. + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, + 'get_collations.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['collation'], + 'value': row['collation']} + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_types(self, gid, sid, did, scid, tid=None): + """ + This function will return list of types available + as AJAX response. + """ + res = [] + try: + SQL = render_template("/".join([self.template_path, + 'get_types.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + # Attaching properties for precession + # & length validation for current type + precision = False + length = False + min_val = 0 + max_val = 0 + + # Check against PGOID for specific type + if row['elemoid']: + if row['elemoid'] in (1560, 1561, 1562, 1563, 1042, 1043, + 1014, 1015): + typeval = 'L' + elif row['elemoid'] in (1083, 1114, 1115, 1183, 1184, 1185, + 1186, 1187, 1266, 1270): + typeval = 'D' + elif row['elemoid'] in (1231, 1700): + typeval = 'P' + else: + typeval = ' ' + + # Logic to set precision & length/min/max values + if typeval == 'P': + precision = True + + if precision or typeval in ('L', 'D'): + length = True + min_val = 0 if typeval == 'D' else 1 + if precision: + max_val = 1000 + elif min_val: + # Max of integer value + max_val = 2147483647 + else: + max_val = 10 + + res.append( + {'label': row['typname'], 'value': row['typname'], + 'typval': typeval, 'precision': precision, + 'length': length, 'min_val': min_val, 'max_val': max_val + } + ) + + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_subtypes(self, gid, sid, did, scid, tid=None): + """ + This function will return list of subtypes available + as AJAX response. + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + subtype=True) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['stype'], 'value': row['stype'], + 'is_collate': row['is_collate']} + ) + + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_subtype_opclass(self, gid, sid, did, scid, tid=None): + """ + This function will return list of subtype opclass available + as AJAX response. + """ + res = [{'label': '', 'value': ''}] + data = request.args + + try: + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + subtype_opclass=True, data=data) + if SQL: + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['opcname'], + 'value': row['opcname']}) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_subtype_diff(self, gid, sid, did, scid, tid=None): + """ + This function will return list of subtypes diff functions available + as AJAX response. + """ + res = [{'label': '', 'value': ''}] + data = request.args + + try: + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + get_opcintype=True, data=data) + if SQL: + status, opcintype = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=opcintype) + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + opcintype=opcintype, conn=self.conn) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['stypdiff'], + 'value': row['stypdiff']} + ) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_canonical(self, gid, sid, did, scid, tid=None): + """ + This function will return list of canonical functions available + as AJAX response. + """ + res = [{'label': '', 'value': ''}] + data = request.args + canonical = True + + try: + # We want to send data only if in we are in edit mode + # else we will disable the combobox + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + getoid=True, data=data) + if SQL: + status, oid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=oid) + # If oid is None then do not run SQL + if oid is None: + canonical = False + + SQL = render_template("/".join([self.template_path, + 'get_subtypes.sql']), + canonical=canonical, conn=self.conn, oid=oid) + if SQL: + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['canonical'], + 'value': row['canonical']}) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def get_external_functions_list(self, gid, sid, did, scid, tid=None): + """ + This function will return list of external functions available + as AJAX response. + """ + res = [{'label': '', 'value': '', 'cbtype': 'all'}] + + try: + # The SQL generated below will populate Input/Output/Send/ + # Receive/Analyze/TypModeIN/TypModOUT combo box + SQL = render_template("/".join([self.template_path, + 'get_external_functions.sql']), + extfunc=True) + if SQL: + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['func'], 'value': row['func'], + 'cbtype': 'all'}) + + # The SQL generated below will populate TypModeIN combo box + SQL = render_template("/".join([self.template_path, + 'get_external_functions.sql']), + typemodin=True) + if SQL: + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['func'], 'value': row['func'], + 'cbtype': 'typmodin'}) + + # The SQL generated below will populate TypModeIN combo box + SQL = render_template("/".join([self.template_path, + 'get_external_functions.sql']), + typemodout=True) + if SQL: + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['func'], 'value': row['func'], + 'cbtype': 'typmodout'}) + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will creates new the type object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + data = request.form if request.form else json.loads(request.data.decode()) + required_args = { + 'name': 'Name', + 'typtype': 'Type' + } + + 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)." % + required_args[arg] + ) + ) + # Additional checks goes here + # If type is composite then check if it has two members + if data and data[arg] == 'c': + if len(data['composite']) < 2: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + 'Composite types requires at least two members' + ) + ) + # If type is enum then check if it has minimum one label + if data and data[arg] == 'e': + if len(data['enum']) < 1: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + 'Enumeration types requires at least one label' + ) + ) + # If type is range then check if subtype is defined or not + if data and data[arg] == 'r': + if data['typname'] is None: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + 'Subtype must be defined for range types' + ) + ) + # If type is external then check if input/output + # conversion function is defined + if data and data[arg] == 'b': + if data['typinput'] is None or \ + data['typoutput'] is None: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + 'External types require both Input & \ + Output conversion function.' + ) + ) + + # To format privileges coming from client + if 'typacl' in data and data['typacl'] is not None: + data['typacl'] = parse_priv_to_db(data['typacl'], self.acl) + + data = self._convert_for_sql(data) + + try: + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_oid.sql']), + scid=scid, data=data) + status, tid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + tid, + scid, + data['name'], + icon="icon-type" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid): + """ + This function will updates existing the type object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + + data = request.form if request.form else json.loads(request.data.decode()) + try: + SQL = self.get_sql(gid, sid, data, scid, tid) + if SQL 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="Type updated", + data={ + 'id': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': tid, + 'scid': scid, + '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, tid): + """ + This function will updates existing the type object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, 'delete.sql']), + data=data, cascade=cascade, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Type dropped"), + data={ + 'id': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def msql(self, gid, sid, did, scid, tid=None): + """ + This function will generates modified sql for type object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + req = request.args + data = dict() + + # converting nested request data in proper json format + for key,val in req.items(): + if key in ['composite', 'enum', 'seclabels', 'typacl']: + data[key] = json.loads(val) + else: + data[key] = val + + try: + SQL = self.get_sql(gid, sid, data, scid, tid) + + if SQL and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + except Exception as e: + internal_server_error(errormsg=str(e)) + + def _convert_for_sql(self, data): + """ + This function will convert combobox values into + readable format for sql & msql function + """ + # Convert combobox value into readable format + + if 'typstorage' in data and data['typstorage'] is not None: + if data['typstorage'] == 'p': + data['typstorage'] = 'PLAIN' + elif data['typstorage'] == 'e': + data['typstorage'] = 'EXTERNAL' + elif data['typstorage'] == 'm': + data['typstorage'] = 'MAIN' + elif data['typstorage'] == 'x': + data['typstorage'] = 'EXTENDED' + + if 'typalign' in data and data['typalign'] is not None: + if data['typalign'] == 'c': + data['typalign'] = 'char' + elif data['typalign'] == 's': + data['typalign'] = 'int2' + elif data['typalign'] == 'i': + data['typalign'] = 'int4' + elif data['typalign'] == 'd': + data['typalign'] = 'double' + + return data + + def get_sql(self, gid, sid, data, scid, tid=None): + """ + This function will genrate sql from model data + """ + if tid is not None: + + for key in ['typacl']: + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = parse_priv_to_db(data[key]['added'], self.acl) + if 'changed' in data[key]: + data[key]['changed'] = parse_priv_to_db(data[key]['changed'], self.acl) + if 'deleted' in data[key]: + data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], self.acl) + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + old_data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, 'acl.sql']), + scid=scid, tid=tid) + status, acl = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + old_data['typacl'] = [] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + if row['deftype'] in old_data: + old_data[row['deftype']].append(priv) + else: + old_data[row['deftype']] = [priv] + + # Calling function to check and additional properties if available + old_data.update(self.additional_properties(old_data, tid)) + old_data = self._convert_for_sql(old_data) + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = [ + 'name', + 'typtype' + ] + + for arg in required_args: + if arg not in data: + return " --definition incomplete" + + # Privileges + if 'typacl' in data and data['typacl'] is not None: + data['typacl'] = parse_priv_to_db(data['typacl'], self.acl) + data = self._convert_for_sql(data) + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + + return SQL + + + @check_precondition + def sql(self, gid, sid, did, scid, tid): + """ + This function will generates reverse engineered sql for type object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, 'acl.sql']), + scid=scid, tid=tid) + status, acl = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + data['typacl'] = [] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + if row['deftype'] in data: + data[row['deftype']].append(priv) + else: + data[row['deftype']] = [priv] + + # Privileges + if 'typacl' in data and data['typacl'] is not None: + data['nspacl'] = parse_priv_to_db(data['typacl'], self.acl) + + # Calling function to check and additional properties if available + data.update(self.additional_properties(data, tid)) + + # We do not want to display table which has '-' value + # setting them to None so that jinja avoid displaying them + for k in data: + if data[k] == '-': + data[k] = None + + SQL = self.get_sql(gid, sid, data, scid, tid=None) + + # We are appending headers here for sql panel + sql_header = "-- Type: {0}\n\n-- ".format(data['name']) + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + SQL = sql_header + '\n\n' + SQL + + return ajax_response(response=SQL) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid): + """ + This function get the dependents and return ajax response + for the type node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + dependents_result = self.get_dependents( + self.conn, tid + ) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid): + """ + This function get the dependencies and return ajax response + for the type node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Type ID + """ + dependencies_result = self.get_dependencies( + self.conn, tid + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + +TypeView.register_node_view(blueprint) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/coll-type.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/coll-type.png new file mode 100644 index 0000000000000000000000000000000000000000..fb020d7d99f84439046d288615e865ee1fbdb815 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv3GfMV1=3NcXI=aM>fHaQT@Q91 z`hS1R{~OExU!3;;MEm~(mH&6<{olN3-cDJdI>wS9zhDN3XE)M-9L@rd$YLPv0mg18 zv+aP47*7|+5RU7%XQO!=40zlgo^wo$EU!My#3j(ks*}LT9dUr^nFV*x`64!{5l*E!$tK_0oAjM#0U}&IgXryak7-D2#Wnye)VybOmYGq(B@15Q% q6b-rgDVb@N5Df;FU=2XkCRPS!5DllMhpqu?VDNPHb6Mw<&;$UZ$8*O3 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/type.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/img/type.png new file mode 100644 index 0000000000000000000000000000000000000000..6c16764e7d08c56922a97a2f0c6cc06455c86a56 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv5AX?b1=8~#9EmzT>)QWU=l(xE z`v2js|F_rtzdY~%nF;@oHvQjQ{(pPk|IMk)0@*;Nj3q&S!3+-1ZlnP@oCO|{#X#Bv zjNMLV+W{G&o-U3d9M_W*4zM_RIV|8v(U4|t6r8Z|5fh7_LtB=H01KmJR;I%QmXsCK znH_-=7a3W69onAxD9m6<$ym$O2Ll0cv3IboFyt=akR{01h5&qyPW_ literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/js/type.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/js/type.js new file mode 100644 index 000000000..5d7944962 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/js/type.js @@ -0,0 +1,863 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'alertify', 'backgrid', 'pgadmin.backgrid', + 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, alertify, Backgrid) { + + if (!pgBrowser.Nodes['coll-type']) { + var databases = pgAdmin.Browser.Nodes['coll-type'] = + pgAdmin.Browser.Collection.extend({ + node: 'type', + label: '{{ _('Types') }}', + type: 'coll-type', + columns: ['name', 'typeowner', 'description'] + }); + }; + + // Security label model declaration + var SecurityModel = Backform.SecurityModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + provider: undefined, + security_label: undefined + }, + schema: [{ + id: 'provider', label: '{{ _('Provider') }}', + type: 'text', disabled: false, cellHeaderClasses:'width_percent_50' + },{ + id: 'security_label', label: '{{ _('Security Label') }}', + type: 'text', disabled: false, cellHeaderClasses:'width_percent_50' + }], + validate: function() { + var err = {}, + errmsg = null, + data = this.toJSON(); + + if (_.isUndefined(this.get('security_label')) || + _.isNull(this.get('security_label')) || + String(this.get('security_label')).replace(/^\s+|\s+$/g, '') == '') { + errmsg = '{{ _('Please provide the value for security label.') }}'; + this.errorModel.set('security_label', errmsg); + return errmsg; + } else { + this.errorModel.unset('security_label'); + } + return null; + } + }); + + // Composite type model declaration + var CompositeModel = Backform.CompositeModel = pgAdmin.Browser.Node.Model.extend({ + idAttribute: 'attnum', + defaults: { + attnum: undefined, + member_name: undefined, + type: undefined, + tlength: undefined, + is_tlength: false, + precision: undefined, + is_precision: false, + collation: undefined, + min_val: undefined, + max_val: undefined, + }, + type_options: undefined, + subtypes: undefined, + schema: [{ + id: 'member_name', label: '{{ _('Member Name') }}', + type: 'text', disabled: false, editable: false + },{ + id: 'type', label: '{{ _('Type') }}', control: 'node-ajax-options', + type: 'text', url: 'get_types', disabled: false, node: 'type', + editable: false, + transform: function(d){ + this.model.type_options = d; + return d; + } + },{ + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'tlength', label: '{{ _('Length/precision') }}', deps: ['type'], type: 'text', + editable: false, + disabled: function(m) { + // We will store type from selected from combobox + var of_type = m.get('type'); + if(m.type_options) { + // iterating over all the types + _.each(m.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + // if length is allowed for selected type + if(o.length) + { + // set the values in model + m.set('is_tlength', true, {silent: true}); + m.set('min_val', o.min_val, {silent: true}); + m.set('max_val', o.max_val, {silent: true}); + } + } + }); + } + return !m.get('is_tlength'); + } + },{ + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'precision', label: '{{ _('Scale') }}', deps: ['type'], + type: 'text', editable: false, + disabled: function(m) { + // We will store type from selected from combobox + var of_type = m.get('type'); + if(m.type_options) { + // iterating over all the types + _.each(m.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + // if precession is allowed for selected type + if(o.precision) + { + // set the values in model + m.set('is_precision', true, {silent: true}); + m.set('min_val', o.min_val, {silent: true}); + m.set('max_val', o.max_val, {silent: true}); + } + } + }); + } + return !m.get('is_precision'); + } + },{ + id: 'collation', label: '{{ _('Collation') }}', + control: 'node-ajax-options', editable: false, + type: 'text', disabled: false, url: 'get_collations', node: 'type' + }], + validate: function() { + var err = {}, + errmsg = null, + changedAttrs = this.sessAttrs; + // Clearing previous errors first. + this.errorModel.clear(); + // Validation for member name + if ( _.has(changedAttrs, 'member_name') && _.isUndefined(this.get('member_name')) || + _.isNull(this.get('member_name')) || + String(this.get('member_name')).replace(/^\s+|\s+$/g, '') == '') { + errmsg = '{{ _('Please specify the value for member name.') }}'; + this.errorModel.set('member_name', errmsg) + return errmsg; + } + // Validation for Length/precision field (see comments above if confused about the naming!) + else if (_.has(changedAttrs, 'tlength') && this.get('is_tlength') + && !_.isUndefined(this.get('tlength'))) { + if (this.get('tlength') < this.get('min_val')) + errmsg = '{{ _('Length/precision should not be less than ') }}' + this.get('min_val'); + if (this.get('tlength') > this.get('max_val') ) + errmsg = '{{ _('Length/precision should not be greater than ') }}' + this.get('max_val'); + // If we have any error set then throw it to user + if(errmsg) { + this.errorModel.set('tlength', errmsg) + return errmsg; + } + } + // Validation for scale field (see comments above if confused about the naming!) + else if (_.has(changedAttrs, 'precision') && this.get('is_precision') + && !_.isUndefined(this.get('precision'))) { + if (this.get('precision') < this.get('min_val')) + errmsg = '{{ _('Scale should not be less than ') }}' + this.get('min_val'); + if (this.get('precision') > this.get('max_val')) + errmsg = '{{ _('Scale should not be greater than ') }}' + this.get('max_val'); + // If we have any error set then throw it to user + if(errmsg) { + this.errorModel.set('precision', errmsg) + return errmsg; + } + } + return null; + } + }); + + var EnumModel = Backform.EnumModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + label: undefined, + }, + schema: [{ + id: 'label', label: '{{ _('Label') }}',type: 'text', disabled: false, + cellHeaderClasses: 'width_percent_99', editable: function(m) { + return _.isUndefined(m.get('label')); + } + }], + validate: function() { + var err = {}, + errmsg = null; + + if (_.isUndefined(this.get('label') || + _.isNull(this.get('label')) || + String(this.get('label')).replace(/^\s+|\s+$/g, '') == '')) { + errmsg = '{{ _('Please specify the value for label.') }}'; + this.errorModel.set('label', errmsg) + return errmsg; + } else { + this.errorModel.unset('label'); + } + return null; + } + }); + + if (!pgBrowser.Nodes['type']) { + pgAdmin.Browser.Nodes['type'] = pgBrowser.Node.extend({ + type: 'type', + label: '{{ _('Type') }}', + collection_type: 'coll-type', + hasSQL: true, + hasDepends: true, + parent_type: ['schema', 'catalog'], + Init: function() { + /* Avoid multiple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_type_on_coll', node: 'coll-type', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Type...') }}', + icon: 'wcTabIcon icon-type', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_type', node: 'type', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Type...') }}', + icon: 'wcTabIcon icon-type', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_type', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Type...') }}', + icon: 'wcTabIcon icon-type', data: {action: 'create', check: false}, + enable: 'canCreate' + } + ]); + + }, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + ext_funcs: undefined, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + is_sys_type: false, + typtype: undefined + }, + + // Default values! + initialize: function(attrs, args) { + var isNew = (_.size(attrs) === 0); + + if (isNew) { + var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user; + var schemaInfo = args.node_info.schema; + + this.set({'typeowner': userInfo.name}, {silent: true}); + this.set({'schema': schemaInfo.label}, {silent: true}); + } + pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments); + }, + + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema' + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'], disabled: true + },{ + id: 'typeowner', label:'{{ _('Owner') }}', cell: 'string', + control: 'node-list-by-name', + type: 'text', mode: ['properties', 'create', 'edit'], node: 'role', + disabled: 'inSchema' + },{ + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], node: 'schema', + disabled: 'inSchema', filter: function(d) { + // If schema name start with pg_* then we need to exclude them + if(d && d.label.match(/^pg_/)) + { + return false; + } + return true; + }, + 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; + } + }) + },{ + id: 'typtype', label:'{{ _('Type') }}', + mode: ['create','edit'], disabled: 'inSchemaWithModelCheck', + group: '{{ _('Definition') }}', + mode: ['edit', 'create'], + select2: { width: "50%" }, + options: function() { + if(!this.model.isNew()) { + return [ + {label: "Composite", value: "c"}, + {label: "Enumeration", value: "e"}, + {label: "External", value: "b"}, + {label: "Range", value: "r"}, + ] + } else { + return [ + {label: "Composite", value: "c"}, + {label: "Enumeration", value: "e"}, + {label: "Range", value: "r"}, + ] + + } + }, + disabled: 'inSchemaWithModelCheck', + // If create mode then by default open composite type + control: Backform.Select2Control.extend({ + render: function(){ + // Initialize parent's render method + Backform.Select2Control.prototype.render.apply(this, arguments); + if(this.model.isNew()) { + this.model.set({'typtype': 'c'}); + } + return this; + } + }) + },{ + id: 'composite', label: '{{ _('Composite Type') }}', + model: CompositeModel, editable: true, type: 'collection', + group: '{{ _('Definition') }}', mode: ['edit', 'create'], + control: 'unique-col-collection', uniqueCol : ['member_name'], + canAdd: true, canEdit: true, canDelete: true, disabled: 'inSchema', + deps: ['typtype'], deps: ['typtype'], + visible: function(m) { + if (m.get('typtype') === 'c') { + return true; + } + return false; + } + },{ + id: 'enum', label: '{{ _('Enumeration Type') }}', + model: EnumModel, editable: true, type: 'collection', + group: '{{ _('Definition') }}', mode: ['edit', 'create'], + canAdd: true, canEdit: false, canDelete: function(m) { + // We will disable it if it's in 'edit' mode + if (m.isNew()) { + return true; + } else { + return false; + } + }, + disabled: 'inSchema', deps: ['typtype'], + control: 'unique-col-collection', uniqueCol : ['label'], + visible: function(m) { + return m.get('typtype') === 'e'; + } + },{ + // We will disable range type control in edit mode + type: 'nested', control: 'plain-fieldset', group: '{{ _('Definition') }}', + mode: ['edit', 'create'], + visible: function(m) { + return m.get('typtype') === 'r'; + }, deps: ['typtype'], label: '{{ _('') }}', + schema:[{ + id: 'typname', label:'{{ _('Sub-type') }}', cell: 'string', + control: 'node-ajax-options', + select2: { allowClear: true, placeholder: "", width: "100%" }, + url: 'get_stypes', type: 'text', mode: ['properties', 'create', 'edit'], + group: '{{ _('Range Type') }}', disabled: 'inSchemaWithModelCheck', + transform: function(d){ + this.model.subtypes = d; + return d; + } + },{ + id: 'opcname', label:'{{ _('Sub-type operator class') }}', cell: 'string', + mode: ['properties', 'create', 'edit'], group: '{{ _('Range Type') }}', + disabled: 'inSchemaWithModelCheck', deps: ['typname'], + control: 'select', options: function() { + var l_typname = this.model.get('typname'), + self = this, + result = []; + if(!_.isUndefined(l_typname) && l_typname != '') + { + var node = this.field.get('schema_node'), + _url = node.generate_url.apply( + node, [ + null, 'get_subopclass', this.field.get('node_data'), false, + this.field.get('node_info') + ]); + $.ajax({ + async: false, + url: _url, + cache: false, + data: {'typname' : l_typname}, + success: function(res) { + result = res.data; + }, + error: function() { + self.model.trigger('pgadmin:view:fetch:error', self.model, self.field); + } + }); + // + } + return result; + } + },{ + id: 'collname', label:'{{ _('Collation') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + group: '{{ _('Range Type') }}', + deps: ['typname'], control: 'node-ajax-options', url: 'get_collations', + select2: { allowClear: true, placeholder: "", width: "100%" }, + disabled: function(m) { + if(this.node_info && 'catalog' in this.node_info) + { + return true; + } + + // Disbale in edit mode + if (!m.isNew()) { + return true; + } + + // To check if collation is allowed? + var of_subtype = m.get('typname'), + is_collate = undefined; + if(!_.isUndefined(of_subtype)) { + // iterating over all the types + _.each(m.subtypes, function(s) { + // if subtype from selected from combobox matches + if ( of_subtype === s.label ) { + // if collation is allowed for selected subtype + // then enable it else disable it + is_collate = s.is_collate; + } + }); + } + // If is_collate is true then do not disable + return is_collate ? false : true; + } + },{ + id: 'rngcanonical', label:'{{ _('Canonical function') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + group: '{{ _('Range Type') }}', + disabled: 'inSchemaWithModelCheck', deps: ['name', 'typname'], + control: 'select', options: function() { + var name = this.model.get('name'), + self = this, + result = []; + + if(!_.isUndefined(name) && name != '') + { + var node = this.field.get('schema_node'), + _url = node.generate_url.apply( + node, [ + null, 'get_canonical', this.field.get('node_data'), false, + this.field.get('node_info') + ]); + $.ajax({ + async: false, + url: _url, + cache: false, + data: {"name" : name}, + success: function(res) { + result = res.data; + }, + error: function() { + self.model.trigger('pgadmin:view:fetch:error', + self.model, self.field); + } + }); + } + return result; + } + },{ + id: 'rngsubdiff', label:'{{ _('Sub-type diff function') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + group: '{{ _('Range Type') }}', + disabled: 'inSchemaWithModelCheck', deps: ['opcname'], + control: 'select', options: function() { + var l_typname = this.model.get('typname'), + l_opcname = this.model.get('opcname'), + self = this, + result = []; + + if(!_.isUndefined(l_typname) && l_typname != '' && + !_.isUndefined(l_opcname) && l_opcname != '') { + var node = this.field.get('schema_node'), + _url = node.generate_url.apply( + node, [ + null, 'get_stypediff', + this.field.get('node_data'), false, + this.field.get('node_info') + ]); + $.ajax({ + async: false, + url: _url, + cache: false, + data: {'typname' : l_typname, 'opcname': l_opcname}, + success: function(res) { + result = res.data; + }, + error: function() { + self.model.trigger('pgadmin:view:fetch:error', + self.model, self.field); + } + }); + } + return result; + } + }] + },{ + type: 'nested', control: 'tab', group: '{{ _('Definition') }}', + label: '{{ _('External Type') }}', deps: ['typtype'], + mode: ['edit'], + visible: function(m) { + return m.get('typtype') === 'b'; + }, + schema:[{ + id: 'typinput', label:'{{ _('Input function') }}', + cell: 'string',type: 'text', + mode: ['properties', 'create', 'edit'], group: 'Required', + disabled: 'inSchemaWithModelCheck', + control: 'node-ajax-options', url: 'get_external_functions', + transform: 'external_func_combo', + select2: { allowClear: true, placeholder: "", width: "100%" } + },{ + id: 'typoutput', label:'{{ _('Output function') }}', + cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + group: 'Required', + disabled: 'inSchemaWithModelCheck' + ,control: 'node-ajax-options', url: 'get_external_functions', + transform: 'external_func_combo', + select2: { allowClear: true, placeholder: "", width: "100%" } + },{ + id: 'typreceive', label:'{{ _('Receive function') }}', + cell: 'string', type: 'text', group: 'Optional-1', + mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck' + ,control: 'node-ajax-options', url: 'get_external_functions', + transform: 'external_func_combo', + select2: { allowClear: true, placeholder: "", width: "100%" } + },{ + id: 'typsend', label:'{{ _('Send function') }}', + cell: 'string', group: 'Optional-1', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck' + ,control: 'node-ajax-options', url: 'get_external_functions', + transform: 'external_func_combo', + select2: { allowClear: true, placeholder: "", width: "100%" } + },{ + id: 'typmodin', label:'{{ _('Typmod in function') }}', + cell: 'string', type: 'text', + mode: ['properties', 'create', 'edit'], group: 'Optional-1', + disabled: 'inSchemaWithModelCheck', + control: 'node-ajax-options', url: 'get_external_functions', + select2: { allowClear: true, placeholder: "", width: "100%" }, + transform: function(d) { + var result = [{label :"", value : ""}]; + _.each(d, function(item) { + // if type from selected from combobox matches in options + if ( item.cbtype === 'typmodin' || item.cbtype === 'all') { + result.push(item); + } + }); + return result; + } + },{ + id: 'typmodout', label:'{{ _('Typmod out function') }}', + cell: 'string', group: 'Optional-1', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck', + control: 'node-ajax-options', url: 'get_external_functions', + select2: { allowClear: true, placeholder: "", width: "100%" }, + transform: function(d) { + var result = [{label :"", value : ""}]; + _.each(d, function(item) { + // if type from selected from combobox matches in options + if ( item.cbtype === 'typmodout' || item.cbtype === 'all') { + result.push(item); + } + }); + return result; + } + },{ + id: 'typlen', label:'{{ _('Internal length') }}', + cell: 'integer', group: 'Optional-1', + type: 'int', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck' + },{ + id: 'variable', label:'{{ _('Variable?') }}', cell: 'switch', + group: 'Optional-1', type: 'switch', + mode: ['create','edit'], + disabled: 'inSchemaWithModelCheck' + },{ + id: 'typdefault', label:'{{ _('Default?') }}', + cell: 'string', group: 'Optional-1', + type: 'text', mode: ['properties', 'create','edit'], + disabled: 'inSchemaWithModelCheck' + },{ + id: 'typanalyze', label:'{{ _('Analyze function') }}', + cell: 'string', group: 'Optional-1', + type: 'text', mode: ['properties', 'create','edit'], + disabled: 'inSchemaWithModelCheck' + ,control: 'node-ajax-options', url: 'get_external_functions', + transform: 'external_func_combo', + select2: { allowClear: true, placeholder: "", width: "100%" } + },{ + id: 'typcategory', label:'{{ _('Category type') }}', + cell: 'string', group: 'Optional-1', + type: 'text', mode: ['properties', 'create','edit'], + disabled: 'inSchemaWithModelCheck', control: 'select2', + select2: { allowClear: true, placeholder: "", width: "100%" }, + options: [ + {label :"", value : ""}, + {label :"Array types", value : "A"}, + {label :"Boolean types", value : "B"}, + {label :"Composite types", value : "C"}, + {label :"Date/time types", value : "D"}, + {label :"Enum types", value : "E"}, + {label :"Geometric types", value : "G"}, + {label :"Network address types", value : "I"}, + {label :"Numeric types", value : "N"}, + {label :"Pseudo-types", value : "P"}, + {label :"String types", value : "S"}, + {label :"Timespan types", value : "T"}, + {label :"User-defined types", value : "U"}, + {label :"Bit-string types", value : "V"}, + {label :"unknown type", value : "X"} + ] + },{ + id: 'typispreferred', label:'{{ _('Preferred?') }}', cell: 'switch', + type: 'switch', mode: ['properties', 'create','edit'], + disabled: 'inSchemaWithModelCheck', + group: 'Optional-1' + },{ + id: 'element', label:'{{ _('Element type') }}', cell: 'string', + control: 'node-ajax-options', group: 'Optional-2', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck', url: 'get_types' + },{ + id: 'typdelim', label:'{{ _('Delimiter') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + group: 'Optional-2', disabled: 'inSchemaWithModelCheck' + },{ + id: 'typalign', label:'{{ _('Alignment type') }}', + cell: 'string', group: 'Optional-2', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck', control: 'select2', + select2: { allowClear: true, placeholder: "", width: "100%" }, + options: [ + {label :"", value : ""}, + {label: "char", value: "c"}, + {label: "int2", value: "s"}, + {label: "int4", value: "i"}, + {label: "double", value: "d"}, + ] + },{ + id: 'typstorage', label:'{{ _('Storage type') }}', + type: 'text', mode: ['properties', 'create', 'edit'], + group: 'Optional-2', cell: 'string', + disabled: 'inSchemaWithModelCheck', control: 'select2', + select2: { allowClear: true, placeholder: "", width: "100%" }, + options: [ + {label :"", value : ""}, + {label: "PLAIN", value: "p"}, + {label: "EXTERNAL", value: "e"}, + {label: "MAIN", value: "m"}, + {label: "EXTENDED", value: "x"}, + ] + },{ + id: 'typbyval', label:'{{ _('Passed by value?') }}', + cell: 'switch', + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: 'Optional-2', + },{ + id: 'is_collatable', label:'{{ _('Collatable?') }}', + cell: 'switch', min_version: 90100, group: 'Optional-2', + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck' + // End of extension tab + }] + },{ + id: 'alias', label:'{{ _('Alias') }}', cell: 'string', + type: 'text', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'type_acl', label:'{{ _('Privileges') }}', cell: 'string', + type: 'text', mode: ['properties'], group: '{{ _('Security') }}', + disabled: 'inSchema' + },{ + id: 'member_list', label:'{{ _('Members') }}', cell: 'string', + type: 'text', mode: ['properties'], group: '{{ _('Definition') }}', + disabled: 'inSchema', visible: function(m) { + if(m.get('typtype') === 'c') { + return true; + } + return false; + } + },{ + id: 'enum_list', label:'{{ _('Labels') }}', cell: 'string', + type: 'text', mode: ['properties'], group: '{{ _('Definition') }}', + disabled: 'inSchema', visible: function(m) { + if(m.get('typtype') === 'e') { + return true; + } + return false; + } + },{ + id: 'is_sys_type', label:'{{ _('System type?') }}', cell: 'switch', + type: 'switch', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'description', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema' + },{ + id: 'typacl', label: 'Privileges', type: 'collection', + group: '{{ _('Security') }}', control: 'unique-col-collection', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({privileges: ['U']}), + mode: ['edit', 'create'], canAdd: true, canDelete: true, + uniqueCol : ['grantee'] + },{ + id: 'seclabels', label: '{{ _('Security Labels') }}', + model: SecurityModel, editable: false, type: 'collection', + group: '{{ _('Security') }}', mode: ['edit', 'create'], + min_version: 90100, canAdd: true, + canEdit: false, canDelete: true, control: 'unique-col-collection' + }], + validate: function() { + // Validation code for required fields + var changedAttrs = this.sessAttrs, + msg = undefined; + + this.errorModel.clear(); + + if (_.has(changedAttrs, 'name') && + (_.isUndefined(this.get('name')) + || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Name can not be empty!') }}'; + this.errorModel.set('name', msg); + } else if (_.has(changedAttrs, 'schema') && + (_.isUndefined(this.get('schema')) + || String(this.get('schema')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Schema can not be empty!') }}'; + this.errorModel.set('schema', msg); + } else if (_.has(changedAttrs, 'typtype') && + (_.isUndefined(this.get('typtype')) + || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Type can not be empty!') }}'; + this.errorModel.set('typtype', msg); + } else if (this.get('typtype') == 'r' && + _.has(changedAttrs, 'typname') + && (_.isUndefined(this.get('typname')) + || String(this.get('typname')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Subtype Name can not be empty!') }}'; + this.errorModel.set('typname', msg); + } else if (this.get('typtype') == 'x' && + _.has(changedAttrs, 'typinput') + && (_.isUndefined(this.get('typinput')) + || String(this.get('typinput')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Input function can not be empty!') }}'; + this.errorModel.set('typinput', msg); + } else if (this.get('typtype') == 'x' && + _.has(changedAttrs, 'typoutput') + && (_.isUndefined(this.get('typoutput')) + || String(this.get('typoutput')).replace(/^\s+|\s+$/g, '') == '')) { + msg = '{{ _('Output function can not be empty!') }}'; + this.errorModel.set('typoutput', msg); + } + 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; + }, + // We will check if we are under schema node & in 'create' mode + inSchemaWithModelCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) + { + // We will disbale control if it's in 'edit' mode + if (m.isNew()) { + return false; + } else { + return true; + } + + } + return true; + }, + // We want to enable only in edit mode + inSchemaWithEditMode: function(m) { + if(this.node_info && 'schema' in this.node_info) + { + // We will disbale control if it's in 'edit' mode + if (m.isNew()) { + return true; + } else { + return false; + } + + } + return true; + }, + // Function will help us to fill combobox + external_func_combo: function(d) { + var result = []; + _.each(d, function(item) { + // if type from selected from combobox matches in options + if ( item.cbtype == 'all' ) { + result.push(item); + } + }); + return result; + } + }), + 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 table + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-type' == d._type) { + //Check if we are not child of catalog + 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['type']; +}); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/acl.sql new file mode 100644 index 000000000..60eab25be --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/acl.sql @@ -0,0 +1,26 @@ +SELECT 'typacl' 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 'USAGE' THEN 'U' + ELSE 'UNKNOWN' + END AS privilege_type + FROM + (SELECT t.typacl + FROM pg_type t + LEFT OUTER JOIN pg_type e ON e.oid=t.typelem + LEFT OUTER JOIN pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c' + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_type'::regclass) + WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid + {% if tid %} + AND t.oid = {{tid}}::oid + {% endif %} + ) 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(t.typacl) as d FROM pg_type t WHERE t.oid = {{tid}}::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 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/additional_properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/additional_properties.sql new file mode 100644 index 000000000..172f6bba4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/additional_properties.sql @@ -0,0 +1,35 @@ +{# The SQL given below will fetch composite type#} +{% if type == 'c' %} +SELECT attnum, attname, format_type(t.oid,NULL) AS typname, attndims, atttypmod, nsp.nspname, + (SELECT COUNT(1) from pg_type t2 WHERE t2.typname=t.typname) > 1 AS isdup, + collname, nspc.nspname as collnspname, att.attrelid +FROM pg_attribute att + JOIN pg_type t ON t.oid=atttypid + JOIN pg_namespace nsp ON t.typnamespace=nsp.oid + LEFT OUTER JOIN pg_type b ON t.typelem=b.oid + LEFT OUTER JOIN pg_collation c ON att.attcollation=c.oid + LEFT OUTER JOIN pg_namespace nspc ON c.collnamespace=nspc.oid + WHERE att.attrelid = {{typrelid}}::oid + ORDER by attnum; +{% endif %} + +{# The SQL given below will fetch enum type#} +{% if type == 'e' %} +SELECT enumlabel +FROM pg_enum + WHERE enumtypid={{tid}}::oid + ORDER by enumsortorder +{% endif %} + +{# The SQL given below will fetch range type#} +{% if type == 'r' %} +SELECT rngsubtype, st.typname, + rngcollation, col.collname, + rngsubopc, opc.opcname, + rngcanonical, rngsubdiff +FROM pg_range + LEFT JOIN pg_type st ON st.oid=rngsubtype + LEFT JOIN pg_collation col ON col.oid=rngcollation + LEFT JOIN pg_opclass opc ON opc.oid=rngsubopc + WHERE rngtypid={{tid}}::oid; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/create.sql new file mode 100644 index 000000000..bd1f8b789 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/create.sql @@ -0,0 +1,79 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{### Composite Type ###} +{% if data and data.typtype == 'c' %} +CREATE TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} AS + ({% if data.composite %}{% for d in data.composite %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(d.member_name) }} {{ d.type }}{% if d.is_tlength and d.tlength %}({{d.tlength}}{% if d.is_precision and d.precision %},{{d.precision}}{% endif %}){% endif %}{% if d.collation %} COLLATE {{d.collation}}{% endif %}{% endfor %}{% endif %}); +{% endif %} +{### Enum Type ###} +{% if data and data.typtype == 'e' %} +CREATE TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} AS ENUM + ({% for e in data.enum %}{% if loop.index != 1 %}, {% endif %}{{ e.label|qtLiteral }}{% endfor %}); +{% endif %} +{### Range Type ###} +{% if data and data.typtype == 'r' %} +CREATE TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} AS RANGE +( + {% if data.typname %}SUBTYPE={{ conn|qtTypeIdent(data.typname) }}{% endif %}{% if data.collname %}, + COLLATION = {{ data.collname }}{% endif %}{% if data.opcname %}, + SUBTYPE_OPCLASS = {{ data.opcname }}{% endif %}{% if data.rngcanonical %}, + CANONICAL = {{ data.rngcanonical }}{% endif %}{% if data.rngsubdiff %}, + SUBTYPE_DIFF = {{ data.rngsubdiff }}{% endif %} + +); +{% endif %} +{### External Type ###} +{% if data and data.typtype == 'b' %} +CREATE TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} + +( + {% if data.typinput %}INPUT = {{data.typinput}}{% endif %}{% if data.typoutput %}, + OUTPUT = {{ data.typoutput }}{% endif %}{% if data.typreceive %}, + RECEIVE = {{data.typreceive}}{% endif %}{% if data.typsend %}, + SEND = {{data.typsend}}{% endif %}{% if data.typmodin %}, + TYPMOD_IN = {{data.typmodin}}{% endif %}{% if data.typmodout %}, + TYPMOD_OUT = {{data.typmodout}}{% endif %}{% if data.typanalyze %}, + ANALYZE = {{data.typanalyze}}{% endif %}{% if data.typlen %}, + INTERNALLENGTH = {{data.typlen}}{% endif %}{% if data.typbyval %}, + PASSEDBYVALUE{% endif %}{% if data.typalign %}, + ALIGNMENT = {{data.typalign}}{% endif %}{% if data.typstorage %}, + STORAGE = {{data.typstorage}}{% endif %}{% if data.typcategory %}, + CATEGORY = {{data.typcategory|qtLiteral}}{% endif %}{% if data.typispreferred %}, + PREFERRED = {{data.typispreferred}}{% endif %}{% if data.typdefault %}, + DEFAULT = {{data.typdefault|qtLiteral}}{% endif %}{% if data.element %}, + ELEMENT = {{data.element}}{% endif %}{% if data.typdelim %}, + DELIMITER = {{data.typdelim|qtLiteral}}{% endif %}{% if data.is_collatable %}, + COLLATABLE = {{data.is_collatable}}{% endif %} + +); +{% endif %} +{### Type Owner ###} +{% if data and data.typeowner %} + +ALTER TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} + + OWNER TO {{data.typeowner}}; +{% endif %} +{### Type Comments ###} +{% if data and data.description %} + +COMMENT ON TYPE {% if data.schema %}{{ conn|qtIdent(data.schema, data.name) }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} + + IS {{data.description|qtLiteral}}; +{% endif %} +{### ACL ###} +{% if data.typacl %} + +{% for priv in data.typacl %} +{{ PRIVILEGE.SET(conn, 'TYPE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{### Security Lables ###} +{% if data.seclabels %} + +{% for r in data.seclabels %} +{% if r.provider and r.security_label %} +{{ SECLABLE.SET(conn, 'TYPE', data.name, r.provider, r.security_label, data.schema) }} +{% endif %} +{% endfor %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..c25882706 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/delete.sql @@ -0,0 +1 @@ +DROP TYPE {{ conn|qtIdent(data.schema, data.name) }}{% if cascade%} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_collations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_collations.sql new file mode 100644 index 000000000..4b0169bc4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_collations.sql @@ -0,0 +1,7 @@ +SELECT --nspname, collname, + CASE WHEN length(nspname) > 0 AND length(collname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(collname)) + ELSE '' END AS collation +FROM pg_collation c, pg_namespace n +WHERE c.collnamespace=n.oid +ORDER BY nspname, collname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_external_functions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_external_functions.sql new file mode 100644 index 000000000..86648a20a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_external_functions.sql @@ -0,0 +1,40 @@ +{### Input/Output/Send/Receive/Analyze function list also append into TypModeIN/TypModOUT ###} +{% if extfunc %} +SELECT proname, nspname, + CASE WHEN length(nspname) > 0 AND length(proname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(proname)) + ELSE '' END AS func +FROM ( + SELECT proname, nspname, max(proargtypes[0]) AS arg0, max(proargtypes[1]) AS arg1 +FROM pg_proc p + JOIN pg_namespace n ON n.oid=pronamespace +GROUP BY proname, nspname +HAVING count(proname) = 1 ) AS uniquefunc +WHERE arg0 <> 0 AND arg1 IS NULL; +{% endif %} +{### TypmodIN list ###} +{% if typemodin %} +SELECT proname, nspname, + CASE WHEN length(nspname) > 0 AND length(proname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(proname)) + ELSE '' END AS func +FROM pg_proc p + JOIN pg_namespace n ON n.oid=pronamespace +WHERE prorettype=(SELECT oid FROM pg_type WHERE typname='int4') + AND proargtypes[0]=(SELECT oid FROM pg_type WHERE typname='_cstring') + AND proargtypes[1] IS NULL +ORDER BY nspname, proname; +{% endif %} +{### TypmodOUT list ###} +{% if typemodout %} +SELECT proname, nspname, + CASE WHEN length(nspname) > 0 AND length(proname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(proname)) + ELSE '' END AS func +FROM pg_proc p + JOIN pg_namespace n ON n.oid=pronamespace +WHERE prorettype=(SELECT oid FROM pg_type WHERE typname='cstring') + AND proargtypes[0]=(SELECT oid FROM pg_type WHERE typname='int4') + AND proargtypes[1] IS NULL +ORDER BY nspname, proname; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..14f79502f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_oid.sql @@ -0,0 +1,11 @@ +{# Below will provide oid for newly created type #} +SELECT t.oid +FROM pg_type t + LEFT OUTER JOIN pg_type e ON e.oid=t.typelem + LEFT OUTER JOIN pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c' + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_type'::regclass) +WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid +{% if data %} + AND t.typname = {{data.name|qtLiteral}} +{% endif %} +ORDER BY t.typname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_subtypes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_subtypes.sql new file mode 100644 index 000000000..75271fe71 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_subtypes.sql @@ -0,0 +1,56 @@ +{### To fill subtype combobox ###} +{% if subtype %} +SELECT DISTINCT typ.typname AS stype, + (CASE WHEN typ.typcollation > 0 THEN true ELSE false END) AS is_collate +FROM pg_opclass opc + JOIN pg_type typ ON opc.opcintype = typ.oid +WHERE opc.opcmethod = 403 +ORDER BY 1 +{% endif %} +{### To fill subtype opclass combobox ###} +{% if subtype_opclass and data and data.typname %} +SELECT opc.opcname +FROM pg_opclass opc + JOIN pg_type typ ON opc.opcintype=typ.oid + AND typ.typname = {{ data.typname|qtLiteral }} +WHERE opc.opcmethod = 403 +ORDER BY opcname; +{% endif %} +{### To fetch opcinttype from subtype opclass ###} +{% if get_opcintype and data and data.typname and data.opcname %} +SELECT opc.opcintype +FROM pg_opclass opc + JOIN pg_type typ ON opc.opcintype=typ.oid + AND typ.typname = {{ data.typname|qtLiteral }} +WHERE opc.opcmethod = 403 + AND opc.opcname = {{ data.opcname|qtLiteral }} +ORDER BY opcname; +{% endif %} +{### To fill subtype diff function combobox ###} +{% if opcintype %} +SELECT proname, nspname, + CASE WHEN length(nspname) > 0 AND length(proname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(proname)) + ELSE '' END AS stypdiff +FROM pg_proc + JOIN pg_namespace n ON n.oid=pronamespace +WHERE prorettype = 701 + AND proargtypes = '{{opcintype}} {{opcintype}}' +ORDER BY proname; +{% endif %} +{### To fill canonical combobox ###} +{% if getoid %} +SELECT oid FROM pg_type +WHERE typname = {{ data.name|qtLiteral }} +{% endif %} +{% if canonical and oid %} +SELECT proname, nspname, + CASE WHEN length(nspname) > 0 AND length(proname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(proname)) + ELSE '' END AS canonical +FROM pg_proc + JOIN pg_namespace n ON n.oid=pronamespace +WHERE prorettype= {{ oid }} + AND proargtypes = '{{ oid }}' +ORDER BY proname; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_types.sql new file mode 100644 index 000000000..2a7d3d865 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/get_types.sql @@ -0,0 +1,10 @@ +SELECT * FROM + (SELECT format_type(t.oid,NULL) AS typname, + CASE WHEN typelem > 0 THEN typelem ELSE t.oid END AS elemoid, + typlen, typtype, t.oid, nspname, + (SELECT COUNT(1) FROM pg_type t2 WHERE t2.typname = t.typname) > 1 AS isdup +FROM pg_type t + JOIN pg_namespace nsp ON typnamespace=nsp.oid +WHERE (NOT (typname = 'unknown' AND nspname = 'pg_catalog')) AND typisdefined AND typtype IN ('b', 'c', 'd', 'e', 'r') AND NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = typname and relkind != 'c') AND (typname not like '_%' OR NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = substring(typname from 2)::name and relkind != 'c')) AND nsp.nspname != 'information_schema' + ) AS dummy +ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..6abcb19a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/nodes.sql @@ -0,0 +1,10 @@ +SELECT t.oid, t.typname AS name +FROM pg_type t + LEFT OUTER JOIN pg_type e ON e.oid=t.typelem + LEFT OUTER JOIN pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c' + LEFT OUTER JOIN pg_namespace nsp ON nsp.oid = t.typnamespace +WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid +{% if not show_system_objects %} + AND ct.oid is NULL +{% endif %} +ORDER BY t.typname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..de261a5ce --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/properties.sql @@ -0,0 +1,24 @@ +SELECT t.oid, t.typname AS name, + (CASE WHEN CAST(coalesce(t.typcollation, '0') AS integer) = 100 THEN true ElSE false END) AS is_collatable, + t.typacl AS type_acl, + t.*, format_type(t.oid, null) AS alias, + pg_get_userbyid(t.typowner) as typeowner, e.typname as element, + description, ct.oid AS taboid, + nsp.nspname AS schema, + --MinimumVersion 9.1 START + (SELECT array_agg(provider || '=' || label) FROM pg_shseclabel sl1 WHERE sl1.objoid=t.oid) AS seclabels, + -- END + (CASE WHEN (t.oid <= {{ datlastsysoid}}::oid OR ct.oid != 0) THEN true ElSE false END) AS is_sys_type +FROM pg_type t + LEFT OUTER JOIN pg_type e ON e.oid=t.typelem + LEFT OUTER JOIN pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c' + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_type'::regclass) + LEFT OUTER JOIN pg_namespace nsp ON nsp.oid = t.typnamespace +WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid +{% if tid %} + AND t.oid = {{tid}}::oid +{% endif %} +{% if not show_system_objects %} + AND ct.oid is NULL +{% endif %} +ORDER BY t.typname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/update.sql new file mode 100644 index 000000000..170b03a87 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/9.1_plus/update.sql @@ -0,0 +1,139 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% if data %} +{#======================================#} +{# Below will change object owner #} +{% if data.typeowner and data.typeowner != o_data.typeowner %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + OWNER TO {{ data.typeowner }}; + +{% endif %} +{#======================================#} +{# Below will change objects comment #} +{% if data.description and data.description != o_data.description %} +COMMENT ON TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + IS {{ data.description|qtLiteral }}; + +{% endif %} +{#======================================#} +{### The sql given below will update composite type ###} +{% if data.composite and data.composite|length > 0 %} +{% set composite = data.composite %} +{% if 'deleted' in composite and composite.deleted|length > 0 %} +{% for r in composite.deleted %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + DROP ATTRIBUTE {{conn|qtIdent(r.member_name)}}; +{% endfor %} +{% endif %} +{% if 'added' in composite and composite.added|length > 0 %} +{% for r in composite.added %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + ADD ATTRIBUTE {{conn|qtIdent(r.member_name)}} {{conn|qtTypeIdent(r.type)}}{% if r.is_tlength and r.tlength %} +({{r.tlength}}{% if r.is_precision and r.precision %},{{r.precision}}{% endif %}){% endif %}{% if r.collation %} + COLLATE {{r.collation}}{% endif %}; +{% endfor %} +{% endif %} +{% if 'changed' in composite and composite.changed|length > 0 %} +{% for r in composite.changed %} +{% for o in o_data.composite %} +{% if o.attnum == r.attnum and r.member_name and o.member_name != r.member_name %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME ATTRIBUTE {{o.member_name}} TO {{r.member_name}}; +{% if r.type and o.type != r.type %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + ALTER ATTRIBUTE {{conn|qtIdent(r.member_name)}} SET DATA TYPE {{conn|qtTypeIdent(r.type)}}{% if r.is_tlength and r.tlength %} +({{r.tlength}}{% if r.is_precision and r.precision %},{{r.precision}}{% endif %}){% endif %}{% if r.collation %} + COLLATE {{r.collation}}{% endif %}; +{% else %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + ALTER ATTRIBUTE {{conn|qtIdent(r.member_name)}} SET DATA TYPE {{conn|qtTypeIdent(o.type)}}{% if o.is_tlength and o.tlength %} +({{o.tlength}}{% if o.is_precision and o.precision %},{{o.precision}}{% endif %}){% endif %}{% if o.collation %} + COLLATE {{r.collation}}{% endif %}; +{% endif%} +{% endif%} +{% endfor %} +{% endfor %} +{% endif %} +{% endif %} +{#======================================#} +{### The sql given below will update enum type ###} +{% if data.enum and data.enum|length > 0 %} +{% set enum = data.enum %} +{% set o_enum_len = o_data.enum|length %} +{# We need actual list index from length #} +{% set o_enum_len = o_enum_len - 1 %} +{% if 'added' in enum and enum.added|length > 0 %} +{% for r in enum.added %} +{% set c_idx = loop.index %} +{% if c_idx == 1 %} +{# if first new element then add it after old data enum list#} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + ADD VALUE {{r.label|qtLiteral}} AFTER {{o_data.enum[o_enum_len].label|qtLiteral }}; +{% else %} +{# if first new element then add it after new data enum list#} +{% set p_idx = loop.index - 2 %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + ADD VALUE {{r.label|qtLiteral}} AFTER {{enum.added[p_idx].label|qtLiteral}}; +{% endif %} +{% endfor %} +{% endif %} + +{% endif %} +{#======================================#} +{# The SQL generated below will change Security Label #} +{% if data.seclabels and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.UNSET(conn, 'TYPE', o_data.name, r.provider, o_data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.SET(conn, 'TYPE', o_data.name, r.provider, r.security_label, o_data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.SET(conn, 'TYPE', o_data.name, r.provider, r.security_label, o_data.schema) }} +{% endfor %} +{% endif %} + +{% endif %} +{#======================================#} +{# Change the privileges #} +{% if data.typacl and data.typacl|length > 0 %} +{% if 'deleted' in data.typacl %} +{% for priv in data.typacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TYPE', priv.grantee, o_data.name, o_data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.typacl %} +{% for priv in data.typacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TYPE', priv.grantee, o_data.name, o_data.schema) }} +{{ PRIVILEGE.SET(conn, 'TYPE', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.typacl %} +{% for priv in data.typacl.added %} +{{ PRIVILEGE.SET(conn, 'TYPE', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{#======================================#} +{# Below will change object name #} +{% if data.name and data.name != o_data.name %} +ALTER TYPE {{ conn|qtIdent(o_data.schema, o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{#======================================#} +{# Below will change the schema for object #} +{# with extra if condition we will also make sure that object has correct name #} +{% if data.schema and data.schema != o_data.schema %} +ALTER TYPE {% if data.name != o_data.name %}{{ conn|qtIdent(o_data.schema, data.name) }} +{% else %}{{ conn|qtIdent(o_data.schema, o_data.name) }}{% endif %} + SET SCHEMA {{ conn|qtIdent(data.schema) }}; + +{% endif %} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/postgres_inbuit_types.txt b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/postgres_inbuit_types.txt new file mode 100644 index 000000000..631037f3f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/templates/type/sql/postgres_inbuit_types.txt @@ -0,0 +1,53 @@ +""" +Here is the list of Postgres inbuilt types & their OID's +We will use these types to check for validations + +## PGOID_TYPE_SERIAL -42L +## PGOID_TYPE_SERIAL8 -43L +## PGOID_TYPE_SERIAL2 -44L +## PGOID_TYPE_BOOL 16L +## PGOID_TYPE_BYTEA 17L +## PGOID_TYPE_CHAR 18L +## PGOID_TYPE_NAME 19L +## PGOID_TYPE_INT8 20L +## PGOID_TYPE_INT2 21L +## PGOID_TYPE_INT4 23L +## PGOID_TYPE_TEXT 25L +## PGOID_TYPE_OID 26L +## PGOID_TYPE_TID 27L +## PGOID_TYPE_XID 28L +## PGOID_TYPE_CID 29L +## PGOID_TYPE_FLOAT4 700L +## PGOID_TYPE_FLOAT8 701L +## PGOID_TYPE_MONEY 790L +## PGOID_TYPE_CHAR_ARRAY 1002L +## PGOID_TYPE_TEXT_ARRAY 1009L +## PGOID_TYPE_BPCHAR_ARRAY 1014L +## PGOID_TYPE_VARCHAR_ARRAY 1015L +## PGOID_TYPE_BPCHAR 1042L +## PGOID_TYPE_VARCHAR 1043L +## PGOID_TYPE_DATE 1082L +## PGOID_TYPE_TIME 1083L +## PGOID_TYPE_TIMESTAMP 1114L +## PGOID_TYPE_TIMESTAMP_ARRAY 1115L +## PGOID_TYPE_TIME_ARRAY 1183L +## PGOID_TYPE_TIMESTAMPTZ 1184L +## PGOID_TYPE_TIMESTAMPTZ_ARRAY 1185L +## PGOID_TYPE_INTERVAL 1186L +## PGOID_TYPE_INTERVAL_ARRAY 1187L +## PGOID_TYPE_NUMERIC_ARRAY 1231L +## PGOID_TYPE_TIMETZ 1266L +## PGOID_TYPE_TIMETZ_ARRAY 1270L +## PGOID_TYPE_BIT 1560L +## PGOID_TYPE_BIT_ARRAY 1561L +## PGOID_TYPE_VARBIT 1562L +## PGOID_TYPE_VARBIT_ARRAY 1563L +## PGOID_TYPE_NUMERIC 1700L +## PGOID_TYPE_CSTRING 2275L +## PGOID_TYPE_ANY 2276L +## PGOID_TYPE_VOID 2278L +## PGOID_TYPE_TRIGGER 2279L +## PGOID_TYPE_LANGUAGE_HANDLER 2280L +## PGOID_TYPE_INTERNAL 2281L +## PGOID_TYPE_HANDLER 3115L +""" \ No newline at end of file