From f466e0169a9544f8b2354b4dd13031b91c99d381 Mon Sep 17 00:00:00 2001 From: Sanket Mehta Date: Wed, 24 Feb 2016 16:44:37 +0000 Subject: [PATCH] Add support for casts. --- .../servers/databases/casts/__init__.py | 643 ++++++++++++++++++ .../databases/casts/static/img/cast.png | Bin 0 -> 426 bytes .../databases/casts/static/img/coll-cast.png | Bin 0 -> 402 bytes .../casts/templates/cast/js/casts.js | 302 ++++++++ .../templates/cast/sql/9.1_plus/create.sql | 20 + .../templates/cast/sql/9.1_plus/delete.sql | 14 + .../templates/cast/sql/9.1_plus/functions.sql | 17 + .../cast/sql/9.1_plus/getsrcandtrgttype.sql | 46 ++ .../cast/sql/9.1_plus/properties.sql | 61 ++ .../casts/templates/cast/sql/9.1_plus/sql.sql | 44 ++ .../templates/cast/sql/9.1_plus/update.sql | 6 + 11 files changed, 1153 insertions(+) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/static/img/cast.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/static/img/coll-cast.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/js/casts.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/functions.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/getsrcandtrgttype.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/sql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/update.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/casts/__init__.py new file mode 100644 index 000000000..e397a6ec3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/__init__.py @@ -0,0 +1,643 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Cast Node""" + +import json +from flask import render_template, make_response, current_app, 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 NodeView +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 + +class CastModule(CollectionNodeModule): + """ + class CastModule(CollectionNodeModule) + + A module class for Cast node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the CastModule and it's base module. + + * get_nodes(gid, sid, did) + - 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 cast, when any of the database node is + initialized. + """ + + NODE_TYPE = 'cast' + COLLECTION_LABEL = 'Casts' + + def __init__(self, *args, **kwargs): + super(CastModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did): + """ + Generate the collection node + :param gid: group id + :param sid: server id + :param did: database id + """ + yield self.generate_browser_collection_node(did) + + @property + def node_inode(self): + """ + Override the property to make the node as leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for cast, when any of the database node is + initialized. + """ + return databases.DatabaseModule.NODE_TYPE + + +blueprint = CastModule(__name__) + + +class CastView(NodeView): + """ + class CastView(NodeView) + + A view class for cast node derived from NodeView. This class is + responsible for all the stuff related to view like create/update/delete cast, + showing properties of cast node, showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the CastView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * 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 cast nodes within that collection. + + * nodes() + - This function will used to create all the child node within that collection. + Here it will create all the cast nodes. + + * properties(gid, sid, did, rg_id) + - This function will show the properties of the selected cast node + + * create(gid, sid, did, rg_id) + - This function will create the new cast object + + * update(gid, sid, did, rg_id) + - This function will update the data for the selected cast node + + * delete(self, gid, sid, rg_id): + - This function will drop the cast object + + * msql(gid, sid, did, rg_id) + - This function is used to return modified SQL for the selected cast node + + * get_sql(data, rg_id) + - This function will generate sql from model data + + * sql(gid, sid, did, rg_id): + - This function will generate sql to show in sql pane for the selected cast node. + + * get_type(): + - This function will fetch all the types for source and target types select control. + + * get_functions(): + - This function will fetch associated functions list depending on selected source + and target types while creating a new cast node. + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'} + ] + ids = [ + {'type': 'int', 'id': 'cid'} + ] + + 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'}], + 'get_type': [{'get': 'get_src_and_trg_type'}, {'get': 'get_src_and_trg_type'}], + 'get_functions': [{'post': 'get_functions'}, {'post': 'get_functions'}] + }) + + def _init_(self, **kwargs): + self.conn = None + self.template_path = None + self.manager = None + super(CastView, self).__init__(**kwargs) + + def module_js(self): + """ + This property defines whether javascript exists for this node. + """ + return make_response( + render_template( + "cast/js/casts.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 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!" + ) + ) + ver = self.manager.version + # we will set template path for sql scripts + if ver >= 90100: + self.template_path = 'cast/sql/9.1_plus' + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did): + """ + This function is used to list all the cast nodes within the collection. + :param gid: group id + :param sid: server id + :param did: database id + :return: + """ + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + datlastsysoid=self.manager.db_info[did]['datlastsysoid'] + ) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + row['castcontext'] = True if row['castcontext'] == 'IMPLICIT' else False + + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did): + """ + This function will used to create all the child nodes within the collection. + Here it will create all the cast nodes. + :param gid: group id + :param sid: server id + :param did: database id + :return: + """ + res = [] + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + datlastsysoid=self.manager.db_info[did]['datlastsysoid'] + ) + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + row['castcontext'] = True if row['castcontext'] == 'IMPLICIT' else False + res.append( + self.blueprint.generate_browser_node( + row['oid'], + did, + row['name'], + icon="icon-cast" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, cid): + """ + This function will show the properties of the selected cast node + :param gid: group id + :param sid: server id + :param did: database id + :param cid: cast id + :return: + """ + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + cid=cid, + datlastsysoid=self.manager.db_info[did]['datlastsysoid'] + ) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + result = res['rows'][0] + + return ajax_response( + response=res['rows'][0], + status=200 + ) + + @check_precondition + def create(self, gid, sid, did): + """ + This function will creates new the cast object + :param did: database id + :param sid: server id + :param gid: group id + :return: + """ + + required_args = [ + 'srctyp', + 'trgtyp' + ] + + 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, + 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, below sql will gives the same + sql = render_template("/".join([self.template_path, 'properties.sql']), + srctyp=data['srctyp'], + trgtyp=data['trgtyp'], + datlastsysoid=self.manager.db_info[did]['datlastsysoid'] + ) + status, cid = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=cid) + + return jsonify( + node=self.blueprint.generate_browser_node( + cid, + did, + data['name'], + icon="icon-cast" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, cid): + """ + This function will update cast object + :param cid: cast id + :param did: database id + :param sid: server id + :param gid: group id + :return: + """ + data = request.form if request.form else json.loads(request.data.decode()) + sql = self.get_sql(gid, sid, did, data, cid) + try: + 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="Cast updated", + data={ + 'id': cid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': cid, + '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, cid): + """ + This function will drop the cast object + :param cid: cast id + :param did: database id + :param sid: server id + :param gid: group id + :return: + """ + # 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: + # Get name for cast from cid + sql = render_template("/".join([self.template_path, 'delete.sql']), + cid=cid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + # drop cast + result = res['rows'][0] + sql = render_template("/".join([self.template_path, 'delete.sql']), + castsource=result['castsource'], + casttarget=result['casttarget'], + 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("Cast dropped"), + data={ + 'id': cid, + '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, cid=None): + """ + This function returns modified SQL + :param cid: cast id + :param did: database id + :param sid: server id + :param gid: group id + :return: + """ + data = request.args + sql = self.get_sql(gid, sid, did, data, cid) + if isinstance(sql, str) and sql 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 + ) + + def get_sql(self, gid, sid, did, data, cid=None): + """ + This function will return sql for model data + :param gid: group id + :param sid: server id + :param did: database id + :param cid: cast id + :param data: model data + :return: + """ + try: + if cid is not None: + sql = render_template("/".join([self.template_path, 'properties.sql']), + cid=cid, + datlastsysoid=self.manager.db_info[did]['datlastsysoid']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + sql = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data + ) + else: + if 'srctyp' in data and 'trgtyp' in data: + sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn) + else: + sql = "-- incomplete definition" + return str(sql) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_functions(self, gid, sid, did, cid=None): + """ + This function will return functions list associated with a cast + :param gid: group id + :param sid: server id + :param did: database id + :param cid: cast id + :return: + """ + res = [] + data = request.form if request.form else json.loads(request.data.decode()) + sql = render_template("/".join([self.template_path, 'functions.sql']), + srctyp=data['srctyp'], + trgtyp=data['trgtyp']) + status, rset = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=rset) + res.append({'label': '', + 'value': ''}) + + for row in rset['rows']: + res.append({'label': row['proname'], + 'value': row['proname']}) + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def get_src_and_trg_type(self, gid, sid, did, cid=None): + """ + This function will return type list + :param gid: group id + :param sid: server id + :param did: database id + :param cid: cast id + :return: + """ + res = [] + sql = render_template( + "/".join([self.template_path, 'getsrcandtrgttype.sql']), + cid=cid + ) + status, rset = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=rset) + + res = [{'label': '', 'value': ''}] + for row in rset['rows']: + res.append({ + 'label': row['typname'], + 'value': row['typname'] + }) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, cid): + """ + This function will generate sql for sql panel + :param gid: group id + :param sid: server id + :param did: database id + :param cid: cast id + :return: + """ + try: + sql = render_template( + "/".join([self.template_path, 'sql.sql']), + cid=cid, + conn=self.conn + ) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error( + _("ERROR: Couldn't generate reversed engineered SQL for the cast!\n{0}").format( + res + ) + ) + + if res is None: + return gone( + _("ERROR: Couldn't generate reversed engineered SQL for the cast node!") + ) + + return ajax_response(response=res) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, cid): + """ + This function gets the dependents and returns an ajax response + for the cast node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + cid: Cast ID + """ + dependents_result = get_dependents(self.conn, cid, 'language') + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, cid): + """ + This function gets the dependencies and returns an ajax response + for the cast node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + cid: Cast ID + """ + dependencies_result = get_dependencies(self.conn, cid, 'language') + return ajax_response( + response=dependencies_result, + status=200 + ) + + + +CastView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/cast.png b/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/cast.png new file mode 100644 index 0000000000000000000000000000000000000000..2be7f3742a760faa7709052669f444ba8949c330 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}a)3{WE0A8=XLIY~`bhs>bEjXs zdtc(<#)_V{^GlchKee~#-UGQ6EB@bqsIhL{|A&u_H*fy`d-wi-{Mh^W@&C`CroDLaf6w;E(Q{(sfz~jV1o;Is zI6S+N2IO!SctjQhX%8@VJDF_ zw)Vt7;XUhu?aCg8-q)T#djsp?1vQn0(#HZ{&avK>G;7M|Kezi*1J|9@wM@A8GIu5a z7k@SvKA%lLfi6)kag8WRNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$Z sGgakKH00)|WTsU@G#FTdHGouG8JIydoSGiG2B?9-)78&qol`;+06z@3hyVZp literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/coll-cast.png b/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/coll-cast.png new file mode 100644 index 0000000000000000000000000000000000000000..09eb65af02c66bd64ab3405c592efe4d90d41c98 GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}RDe&2E08|5w`XaeOt_Z}!tZ4Ap#oci#Q{=SXoPoCQE-(e zAO8R0ecP*7DQD0A|MY3{+qb3HuKmA%|NpaR|1ZhRj0c*|SQ6wH%;50sMjDXAS>O>_ z45U54*zIJt9gval>Eak7ak=#TZN6p&0hSA?yGp&5X6Q%he*0e^ToZZWNTsr+b)t)l zj9%4Lq4qZOwfFDczc{Oq<6)rmksEJ&nEiOFw@y3DVz*ZO=8+TAZ=JQC&Ch=IlJ%q0 zn#a%d$On5}eQ!_{`O0<1PkWs^`g)O!!JAm-u{;Xg4zyae#5JNMC9x#cD!C{XNHG{0 z7#ipr8tEDsh8P)GnHXD{m}ncAS{WEv%v6;_(U6;;l9^Ts(O_T+)&Np%Wnc!;aB6z! Q8lVOSPgg&ebxsLQ09Xf~fdBvi literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/js/casts.js b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/js/casts.js new file mode 100644 index 000000000..4354ee39b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/js/casts.js @@ -0,0 +1,302 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, alertify) { + + // Extend the collection class for cast + if (!pgBrowser.Nodes['coll-cast']) { + var casts = pgAdmin.Browser.Nodes['coll-cast'] = + pgAdmin.Browser.Collection.extend({ + node: 'cast', + label: '{{ _('Casts') }}', + type: 'coll-cast', + columns: ['name', 'description'] + }); + }; + + // Extend the node class for cast + if (!pgBrowser.Nodes['cast']) { + pgAdmin.Browser.Nodes['cast'] = pgAdmin.Browser.Node.extend({ + parent_type: 'database', + type: 'cast', + canDrop: true, + canDropCascade: true, + label: '{{ _('Cast') }}', + hasSQL: true, + hasDepends: true, + Init: function() { + + // Avoid multiple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + // Add context menus for cast + pgBrowser.add_menus([{ + name: 'create_cast_on_database', node: 'database', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Cast...') }}', + icon: 'wcTabIcon icon-cast', data: {action: 'create'} + },{ + name: 'create_cast_on_coll', node: 'coll-cast', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Cast...') }}', + icon: 'wcTabIcon icon-cast', data: {action: 'create'} + },{ + name: 'create_cast', node: 'cast', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Cast...') }}', + icon: 'wcTabIcon icon-cast', data: {action: 'create'} + }]); + + }, + + // Define the backform model for cast node + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, // Name of the cast + encoding: 'UTF8', + srctyp: undefined, // Source type + trgtyp: undefined, // Target type + proname: undefined, // Function + castcontext: undefined, // Context (IMPLICIT/EXPLICIT/ASSIGNMENT) + syscast: undefined, // Is this cast is system object? Yes/No + description: undefined // Comment on the cast + }, + + // Define the schema for cast + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', group: '{{ _('Definition') }}', + editable: false, type: 'text', disabled: true, cellHeaderClasses: 'width_percent_50' + },{ + id: 'oid', label:'{{ _('Oid') }}', cell: 'string', group: '{{ _('Definition') }}', + editable: false, type: 'text', disabled: true + },{ + id: 'srctyp', label:'{{ _('Source type') }}', url: 'get_type', + type: 'text', group: 'Definition', disabled: function(m) { + return !m.isNew() + }, mode: ['create'], + + transform: function(rows) { + _.each(rows, function(r) { + r['image'] = 'icon-cast'; + }); + return rows; + }, + + /* + * Control is extended to create cast name from source type and destination type + * once their values are changed + */ + control: Backform.NodeAjaxOptionsControl.extend({ + + onChange: function() { + Backform.NodeAjaxOptionsControl.prototype.onChange.apply( + this, arguments + ); + + /* + * On source type change, check if both source type and + * target type are set, if yes then fetch values from both + * controls and generate cast name + */ + var srctype = this.model.get('srctyp'); + var trgtype = this.model.get('trgtyp'); + if(srctype != undefined && srctype != '' && + trgtype != undefined && trgtype != '') + this.model.set("name", srctype+"->"+trgtype); + else + this.model.unset("name"); + } + }) + }, + + /* + * Text control for viewing source type in properties and + * edit mode only + */ + { + id: 'srctyp', label:'{{ _('Source type') }}', type: 'text', + group: 'Definition', disabled: true, mode:['properties','edit'] + },{ + id: 'trgtyp', label:'{{ _('Target type') }}', url: 'get_type', + type: 'text', group: 'Definition', disabled: function(m) { + return !m.isNew() + }, mode: ['create'], + transform: function(rows) { + _.each(rows, function(r) { + r['image'] = 'icon-cast'; + }); + return rows; + }, + + /* + * Control is extended to create cast name from source type and destination type + * once their values are changed + */ + control: Backform.NodeAjaxOptionsControl.extend({ + + onChange: function() { + Backform.NodeAjaxOptionsControl.prototype.onChange.apply( + this, arguments + ); + + /* + * on target type change, check if both source type and + * target type are set, if yes then fetch values from both + * controls and generate cast name + */ + var srcType = this.model.get('srctyp'); + var trgtype = this.model.get('trgtyp'); + if(srcType != undefined && srcType != '' && + trgtype != undefined && trgtype != '') + this.model.set("name", srcType+"->"+trgtype); + else + this.model.unset("name"); + } + }) + }, + /* + * Text control for viewing target type in properties and + * edit mode only + */ + { + id: 'trgtyp', label:'{{ _('Target type') }}', type: 'text', + group: 'Definition', disabled: true, mode:['properties','edit'] + }, + + /* + * Proname field is dependent on source type and target type. + * On source and target type changed event, + * associated functions will be fetch using ajax call + */ + { + id: 'proname', label:'{{ _('Function') }}', deps:['srctyp', 'trgtyp'], + type: 'text', disabled: function(m) { return !m.isNew(); }, + group: 'Definition', mode: ['create'], + control: 'node-ajax-options', + options: function() { + + var srcTyp = this.model.get('srctyp'); + var trgtyp = this.model.get('trgtyp'); + var res = []; + + if(srcTyp != undefined && srcTyp != '' && + trgtyp != undefined && trgtyp != '') + { + var node = this.field.get('schema_node'), + _url = node.generate_url.apply( + node, [ + null, 'get_functions', this.field.get('node_data'), false, + this.field.get('node_info') + ]); + $.ajax({ + type: 'POST', + timeout: 30000, + url: _url, + cache: false, + async: false, + data: {"srctyp" : srcTyp, "trgtyp" : trgtyp}, + + // On success return function list from server + success: function(result) { + res = result.data; + return res; + }, + + // On failure show error appropriate error message to user + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + } + }); + } + return res; + } + }, + /* + * Text type control for viewing function name in properties and + * edit mode only + */ + { + id: 'proname', label:'{{ _('Function') }}', type: 'text', + group: 'Definition', disabled: true, mode:['properties','edit'] + },{ + id: 'castcontext', label:'{{ _('Context') }}', + options:{'onText':'IMPLICIT','offText':'EXPLICIT'}, + editable: false, type: 'string', group: 'Definition', + mode:['create'], + control: Backform.SwitchControl.extend({ + getValueFromDOM: function() { + return this.$input.prop('checked') ? 'IMPLICIT' : 'EXPLICIT'; + } + }) + }, + /* + * Text control for viewing context in properties and + * edit mode + */ + { + id: 'castcontext', label:'{{ _('Context') }}', disabled: true, + options:[{ + label: 'IMPLICIT', value: 'IMPLICIT' + },{ + label: 'EXPLICIT', value: 'EXPLICIT' + },{ + label: 'ASSIGNMENT', value: 'ASSIGNMENT' + }], editable: false, type: 'select2', group: 'Definition', + mode:['properties', 'edit'] + },{ + id: 'syscast', label:'{{ _('System Cast?') }}', mode: ['properties'], + editable: false, type: 'text' + },{ + id: 'description', label:'{{ _('Comment') }}',type: 'text', group: 'Definition', + type: 'multiline', cellHeaderClasses: 'width_percent_50' + } + ], + + /* + * Triggers control specific error messages for source type and + * target type if any one of them is not selected while creating + * new cast + */ + validate: function(keys){ + + var srctype = this.get('srctyp'); + var trgtype = this.get('trgtyp'); + + // validate source type control + if (_.isUndefined(srctype) || _.isNull(srctype) || String(srctype).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Source type must be selected!') }}'; + this.errorModel.set('srctyp', msg); + return msg; + } + else + { + this.errorModel.unset('srctyp'); + } + + // validate target type control + if (_.isUndefined(trgtype) || _.isNull(trgtype) || String(trgtype).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Target type must be selected!') }}'; + this.errorModel.set('trgtyp', msg); + return msg; + } + else + { + this.errorModel.unset('trgtyp'); + } + this.trigger('on-status-clear'); + return null; + } + }) + }); + + } + return pgBrowser.Nodes['coll-cast']; +}); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/create.sql new file mode 100644 index 000000000..cef76ca4a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/create.sql @@ -0,0 +1,20 @@ +{# CREATE CAST Statement #} +{% if is_sql %} +-- Cast: {{conn|qtTypeIdent(data.srctyp)}}->{{ conn|qtTypeIdent(data.trgtyp) }}; + +-- DROP CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trgtyp) }}); + +{% endif %} +{% if data and data.srctyp and data.trgtyp %} +CREATE CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trgtyp) }}) +{% if data.proname and data.proname != 'binary compatible'%} + WITH FUNCTION {{data.proname}}{% else %} + WITHOUT FUNCTION{% endif %} +{% if data.castcontext and data.castcontext != 'EXPLICIT' %} + + AS {{data.castcontext}}{% endif %}; +{# Description for CAST #} +{% if data.description %} +COMMENT ON CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trgtyp) }}) + IS {{ data.description|qtLiteral }}; +{% endif %}{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..1ea23431e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/delete.sql @@ -0,0 +1,14 @@ +{# FETCH CAST SOURCE TYPE AND TARGET TYPE Statement #} +{% if cid %} +SELECT + format_type(ca.castsource, null) as castsource, + format_type(ca.casttarget, null) as casttarget +FROM + pg_cast ca +WHERE + ca.oid = {{cid}}::OID; +{% endif %} +{# DROP CAST Statement #} +{% if castsource and casttarget %} +DROP CAST ({{castsource}} AS {{casttarget}}) {% if cascade %}CASCADE{%endif%}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/functions.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/functions.sql new file mode 100644 index 000000000..119516784 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/functions.sql @@ -0,0 +1,17 @@ +{# FETCH FUNCTIONS depending upon SOURCE TYPE and TARGET TYPE IN CAST #} +SELECT + proname || '(' || pg_catalog.pg_get_function_identity_arguments(p.oid) || ')' as proname, + nspname, + proargtypes +FROM + pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace +WHERE + proargtypes[0] = (SELECT t.oid FROM pg_type t WHERE format_type(t.oid, NULL) = {{srctyp|qtLiteral}}) + AND prorettype = (SELECT t.oid FROM pg_type t WHERE format_type(t.oid, NULL) = {{trgtyp|qtLiteral}}) + AND CASE + WHEN array_length(proargtypes,1) = 2 THEN + proargtypes[1] = 23 + WHEN array_length(proargtypes,1) >= 3 THEN + proargtypes[1] = 23 AND proargtypes[2] = 16 + ELSE TRUE + END diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/getsrcandtrgttype.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/getsrcandtrgttype.sql new file mode 100644 index 000000000..4636f873b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/getsrcandtrgttype.sql @@ -0,0 +1,46 @@ +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', '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 diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..15efa951d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/properties.sql @@ -0,0 +1,61 @@ +{# Get OID for CAST #} +{% if srctyp and trgtyp %} + SELECT + ca.oid + FROM pg_cast ca + WHERE ca.castsource = (SELECT t.oid FROM pg_type t WHERE format_type(t.oid, NULL) = {{srctyp|qtLiteral}}) + AND ca.casttarget = (SELECT t.oid FROM pg_type t WHERE format_type(t.oid, NULL) = {{trgtyp|qtLiteral}}) + {% if datlastsysoid %} + AND ca.oid > {{datlastsysoid}}::OID + {% endif %} + +{# FETCH properties for CAST #} +{% else %} + SELECT + ca.oid, + CASE + WHEN {{datlastsysoid}}::OID > ca.oid then 'Yes' ELSE 'No' + END AS syscast, + CASE + WHEN ca.castcontext = 'a' THEN 'ASSIGNMENT' + WHEN ca.castcontext = 'i' THEN 'IMPLICIT' + WHEN ca.castcontext = 'e' THEN 'EXPLICIT' + END AS castcontext, + CASE + WHEN proname IS NULL THEN 'binary compatible' + ELSE proname || '(' || pg_catalog.pg_get_function_identity_arguments(pr.oid) || ')' + END AS proname, + ca.castfunc, + format_type(st.oid,NULL) AS srctyp, + format_type(tt.oid,tt.typtypmod) AS trgtyp, + ns.nspname AS srcnspname, + nt.nspname AS trgnspname, + np.nspname AS pronspname, + description, + concat(format_type(st.oid,NULL),'->',format_type(tt.oid,tt.typtypmod)) as name + FROM pg_cast ca + JOIN pg_type st ON st.oid=castsource + JOIN pg_namespace ns ON ns.oid=st.typnamespace + JOIN pg_type tt ON tt.oid=casttarget + JOIN pg_namespace nt ON nt.oid=tt.typnamespace + LEFT JOIN pg_proc pr ON pr.oid=castfunc + LEFT JOIN pg_namespace np ON np.oid=pr.pronamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=ca.oid AND des.objsubid=0 AND des.classoid='pg_cast'::regclass) + + {% if cid %} + WHERE ca.oid={{cid}}::int + {% endif %} + +--TODO: add check for showSystemObject(). currently assumed as false + {# + {% if datlastsysoid %} + {% if cid %} + AND + {% else %} + WHERE + {% endif %} + ca.oid > {{datlastsysoid}}::OID + {% endif %} + #} + ORDER BY st.typname, tt.typname +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/sql.sql new file mode 100644 index 000000000..0554ad3c6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/sql.sql @@ -0,0 +1,44 @@ +SELECT + array_to_string(array_agg(sql), E'\n\n') as sql +FROM +(SELECT + E'-- Cast: ' || + format_type(st.oid, null)|| E' -> ' || + format_type(tt.oid, tt.typtypmod) || + E'\n\n-- DROP CAST (' || format_type(st.oid, null) || + E' AS ' || format_type(tt.oid,tt.typtypmod) || + E');\n\n CREATE CAST (' || format_type(st.oid, null) || + E' AS ' || format_type(tt.oid,tt.typtypmod) || E')\n' || + CASE WHEN ca.castfunc != 0 THEN + E'\tWITH FUNCTION ' || + pr.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pr.oid), '') || E')' + WHEN ca.castfunc = 0 AND ca.castmethod = 'i' THEN + E'\tWITH INOUT' + ELSE E'\tWITHOUT FUNCTION' END || + CASE WHEN ca.castcontext = 'a' THEN E'\n\tAS ASSIGNMENT;' + WHEN ca.castcontext = 'i' THEN E'\n\tAS IMPLICIT;' + ELSE E';' END || + CASE WHEN a.description IS NOT NULL THEN + E'\n\nCOMMENT ON CAST (' || (format_type(st.oid,NULL)) || + E' AS ' || (format_type(tt.oid,tt.typtypmod)) || + E') IS ' || pg_catalog.quote_literal(description) || E';' + ELSE '' END as sql +FROM + pg_cast ca + JOIN pg_type st ON st.oid=ca.castsource + JOIN pg_namespace ns ON ns.oid=st.typnamespace + JOIN pg_type tt ON tt.oid=ca.casttarget + JOIN pg_namespace nt ON nt.oid=tt.typnamespace + LEFT JOIN pg_proc pr ON pr.oid=ca.castfunc + LEFT JOIN ( + SELECT + des.description as description, + des.objoid as descoid + FROM + pg_description des + WHERE + des.objoid={{cid}}::OID AND des.objsubid=0 AND des.classoid='pg_cast'::regclass + ) a ON (a.descoid = ca.oid) +WHERE + ca.oid={{cid}}::OID +) c; diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/update.sql new file mode 100644 index 000000000..e0bd4e82d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/sql/9.1_plus/update.sql @@ -0,0 +1,6 @@ +{# UPDATE Description for CAST #} + +{% if data and 'description' in data and data.description != o_data.description %} +COMMENT ON CAST ({{ conn|qtTypeIdent(o_data.srctyp) }} AS {{ conn|qtTypeIdent(o_data.trgtyp) }}) + IS {{ data.description|qtLiteral }}; +{% endif %} \ No newline at end of file