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 0000000..edb937b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/__init__.py @@ -0,0 +1,470 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2015, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +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): + 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 + """ + yield self.generate_browser_collection_node(did) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return databases.DatabaseModule.NODE_TYPE + + +blueprint = CastModule(__name__) + + +class CastView(NodeView): + 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' + }], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}], + 'get_type': [{'get': 'get_sourceTarget_type'}, {'get': 'get_sourceTarget_type'}], + 'getfunctions': [{'post': 'get_functions'}, {'post': 'get_functions'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "cast/js/casts.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + ver = self.manager.version + # we will set template path for sql scripts + if ver >= 90000: + self.template_path = 'cast/sql/9.0_plus' + + return f(*args, **kwargs) + + return wrap + + + @check_precondition + def list(self, gid, sid, did): + 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) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did): + 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']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + row['name'], + icon="icon-cast" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, cid): + 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) + + 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 + """ + + 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 + ) + 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, 'grant.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, + data['name'], + icon="pg-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 + """ + data = request.form if request.form else json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, 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 + """ + # 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 and drop it + SQL = render_template("/".join([self.template_path, 'delete.sql']), + cid=cid, + 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 + """ + data = request.args + SQL = self.getSQL(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 getSQL(self, gid, sid, did, data, cid=None): + """ + This function will return SQL for model data + """ + 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) + SQL += "\n" + SQL += render_template("/".join([self.template_path, 'grant.sql']), data=data) + else: + SQL = "-- incomplete definition" + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_functions(self, gid, sid, did, cid=None): + res=[] + data = request.form if request.form else json.loads(request.data.decode()) + #srcOid = data['srctyp'].split(":")[0] + #trgOid = data['trgtyp'].split(":")[0] + 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) + + # TODO: add schemaprefix to proname before adding it to value in res + 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_sourceTarget_type(self, gid, sid, did, cid=None): + 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']: + # TODO: Follow dlgTypeProperty::FillDataType() function before adding typename to res + res.append({'label': row['typname'], + 'value': row['typname']}) + + + print(res) + return make_json_response( + data=res, + status=200 + ) + + + """def getDataType(self, resultRow, numdims=0, typmod = -1): + + nspname = resultRow['nspname'] + typname = resultRow['typname'] + isdup = resultRow['isdup'] + name = "" + array = "" + length = "" + prec = 0 + len = 0 + if str(nspname + '\".') in typname: + name = typname[nspname.len() + 3] # "+2" because of the two double quotes + elif str(nspname + ".") in typname: + name = typname[nspname.len() + 1] + else: + name = typname + + if name.startswith("_"): + if not numdims: + numdims = 1 + name = name[1] + if name[-2:] == str("[]"): + if not numdims: + numdims = 1 + name = name[name.len() - 2] + + if name.startswith('\"') and name.endswith('\"'): + name = name[1, name.len() - 2] + + if numdims > 0: + while numdims-- : + array += str("[]") + + if typmod != -1: + length = str("(") + if name == str("numeric"): + len = (typmod - 4L) >> 16L + prec = (typmod - 4) & 0xffff + length += str(len) + if prec: + length += str(",") + str(prec) + + elif name == str("time") or name == str("timetz") \ + or name == str("time without time zone") or name == str("time with time zone") \ + or name == str("timestamp") or name == str("timestamptz") \ + or name == str("timestamp without time zone") or name == str("timestamp with time zone") \ + or name == str("bit") or name == str("bit varying") or name == str("varbit"): + prec = 0 + len = typmod + length += str(len) + + elif name == str("interval"): + prec = 0 + len = (typmod & 0xffff) + length += str(len) + + elif name == str("date"): + len = prec = 0 + length = str("") #Clear Length + + else: + prec = 0 + len = typmod - 4L + length += str(len) + + if length.len() > 0: + length += str(")") + else: + len = prec = 0""" +CastView.register_node_view(blueprint) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/cast-sm.png b/web/pgadmin/browser/server_groups/servers/databases/casts/static/img/cast-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..52b775538d0fa24e280d595b8d08eccad868fc9f GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}Y=BRQE0Es3wsvWsO@89kNdH}P zr(Zj=J^k)|iTb?irAz-G-&J<+f#RxF|M#vbc=$+vQ&0wv;W_|ExmT_|A+T&_wWDz^l9?5Xa7&_?Wrr6y|pSR3}^vkNswPKgTu2MX+REV zfk$L9koEv$x0Bg+Kt{Bui(`ny<*9wA#T*O-oVD2&UYWh>`2YH4>$a5LxjT3A$!C8<`)MX5lF!N|bSK-bVn*T68u$iT|P*viC2 z+rZSyz`$arsvL@j-29Zxv`UBu152<5kZLOfGl+&$(?iz)H86O(`njxgN@xNAxrdn& literal 0 HcmV?d00001 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 0000000..501b878 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/cast/js/casts.js @@ -0,0 +1,183 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, alertify) { + + if (!pgBrowser.Nodes['coll-cast']) { + var casts = pgAdmin.Browser.Nodes['coll-cast'] = + pgAdmin.Browser.Collection.extend({ + node: 'cast', + label: '{{ _('Casts') }}', + type: 'coll-cast' + }); + }; + + if (!pgBrowser.Nodes['cast']) { + pgAdmin.Browser.Nodes['cast'] = pgAdmin.Browser.Node.extend({ + parent_type: 'database', + type: 'cast', + label: '{{ _('Cast') }}', + hasSQL: true, + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + 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'} + },{ + name: 'drop_cast', node: 'cast', module: this, + applies: ['object', 'context'], callback: 'delete_obj', + category: 'drop', priority: 4, label: '{{ _('Delete/Drop Cast...') }}', + icon: 'fa fa-thrash' + },{ + name: 'drop_cascaded_cast', node: 'cast', module: this, + applies: ['object', 'context'], callback: 'delete_obj_cascade', + category: 'drop', priority: 4, label: '{{ _('Drop Cascaded...') }}', + icon: 'fa fa-thrash' + }]); + + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + comment: undefined, + encoding: 'UTF8', + srctyp: undefined, + trgtyp: undefined, + proname: undefined, + castcontext: undefined, + syscast: undefined, + description: undefined + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', group: '{{ _('Definition') }}', + editable: false, type: 'text', disabled: true + },{ + 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() + }, + transform: function(rows) { + _.each(rows, function(r) { + r['image'] = 'icon-cast'; + }); + return rows; + }, + control: Backform.NodeAjaxOptionsControl.extend({ + onChange: function() { + console.log(this.$el.find("select").val()); + Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments); + 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"); + } + }) + },{ + id: 'trgtyp', label:'{{ _('Target type') }}', url: 'get_type', + type: 'text', group: 'Definition', disabled: function(m) { + return !m.isNew() + }, + transform: function(rows) { + _.each(rows, function(r) { + r['image'] = 'icon-cast'; + }); + return rows; + }, + control: Backform.NodeAjaxOptionsControl.extend({ + onChange: function() { + Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments); + 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"); + } + }) + },{ + id: 'proname', label:'{{ _('Function') }}', deps:['srctyp', 'trgtyp'], + editable: false, type: 'text', disabled: function(m) { return !m.isNew(); }, + group: 'Definition', + control: 'select', options: function() { + /* + * TODO:: + * Make ajax call here and return it as an array of object + * {label: