public inbox for [email protected]
help / color / mirror / Atom feedFrom: Surinder Kumar <[email protected]>
To: Neel Patel <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4] [Patch]: Extension Module
Date: Thu, 4 Feb 2016 11:40:49 +0530
Message-ID: <CAM5-9D-FMDcV8L1FbPiEU4pCeOO1dcLu-62r5CHRuO_ks9hiSw@mail.gmail.com> (raw)
In-Reply-To: <CACCA4P2OFF8mHsRocYpq+YFV5mMZTw1yLOW-FYsHuOvrm7G0UQ@mail.gmail.com>
References: <CAM5-9D9k1MGYxhf-pobDbeDJxOttVawje=JzFesEMgD=fxRR+w@mail.gmail.com>
<CAM5-9D9P6tfPajz1mUi1B03+ypLKsebehkaxWfV_C6k=ZjCSPg@mail.gmail.com>
<CACCA4P3JuzVzoq7DFmyFzjbKaE3uf-P1+4_zkBO6RfxTB-VVSQ@mail.gmail.com>
<CAM5-9D_jZUQMinr4s3upX3ktSM6AV_NWQ-RMQMjyroWMNL3bcg@mail.gmail.com>
<CAM5-9D81Yg5ofY0bjgZvSDM7Gt1t7bTuqgMSmExx1086tMT7Qg@mail.gmail.com>
<CAM5-9D-c2s9bfxVgoZMHbWNsvsiQzyHSMtx2revHz-2vaDYTJw@mail.gmail.com>
<CACCA4P2OFF8mHsRocYpq+YFV5mMZTw1yLOW-FYsHuOvrm7G0UQ@mail.gmail.com>
List-Unsubscribe: <mailto:[email protected]?body=unsub%20pgadmin-hackers>
Hi Neel,
Please find the patch with following changes:
1. Removed Whitespace from files.
2. Fixed an issue in which json object converted into [object object]
string.
3. Fixed python3 issue of unicode type where code breaks at
"isinstance(SQL, str)" in python3 if str is unicode,
because python no longer support unicode type of string.
Please review the patch and Let me know for any comments.
Thanks,
Surinder Kumar
On Thu, Feb 4, 2016 at 10:16 AM, Neel Patel <[email protected]>
wrote:
> Hi Surinder,
>
> While applying the patch, we are getting below warnings.
>
> extension_v3.patch:362: trailing whitespace.
> This function will generate sql for sql panel
> extension_v3.patch:646: trailing whitespace.
> -- Extension: {{ conn|qtIdent(data.name) }}
> warning: 2 lines add whitespace errors.
>
>
> Can you please resend the patch file, after fixing above warnings ?
>
> Thanks,
> Neel Patel
>
> On Fri, Jan 22, 2016 at 12:25 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi
>>
>> Please find the updated patch with following fixes:
>> 1. Missing `owner` column under properties for `extensions collection`.
>> Add cell: 'string' property for owner fixed it
>> 2. Schema object identifier should be wrapped with in function qtIdent .
>> Using function `qtIdent` for schema in create.sql fixed it.
>>
>>
>> Thanks
>> Surinder Kumar
>>
>> On Thu, Jan 21, 2016 at 8:04 PM, Surinder Kumar <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> I've fixed the issues given in review comments.
>>>
>>> Please find the attached updated patch for extension module, review it
>>> and let me know for any comments.
>>>
>>> On Mon, Jan 18, 2016 at 5:44 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Thanks Neel for reviewing. I'll send the patch with the fixes suggested.
>>>>
>>>> On Mon, Jan 18, 2016 at 4:52 PM, Neel Patel <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Surinder,
>>>>>
>>>>> We have applied/tested the patch and below are the review comments.
>>>>>
>>>>> 1. When we select the extension "plpython3u", "plperl", "plperu" etc.
>>>>> then it gives 'TypeError' in Javascript.
>>>>> TypeError: d.version is undefined
>>>>> 'version': (!_.isNull(d.version[0]) ? d.version[0]: '')
>>>>>
>>>>> We are getting this error while selecting many extensions so please
>>>>> test with all types of extensions, it should not give any error at client
>>>>> side.
>>>>>
>>>> Fixed.
>>>
>>>>
>>>>> 2. Use 2 space indentation instead of 4 space in javascript file.
>>>>>
>>>> Done
>>>
>>>>
>>>>> 3. In "validate" function in "extension.js" file, validate only the
>>>>> changed values not all, and "this.get('name') - should be called only one
>>>>> time not multiple
>>>>> time".
>>>>>
>>>> Fixed.
>>>
>>>>
>>>>> 4. When we pass object identifier, use the function 'qtIdent', and
>>>>> for the values, use function 'qtLiteral' in all the sql files.
>>>>>
>>>> Done.
>>>
>>>>
>>>>> 5. By default, when we create the extension, "schema_name" and
>>>>> "version" should not be be set with value. It should be set blank by
>>>>> default.
>>>>>
>>>> Kept blank while creating extension.
>>>
>>>>
>>>>> 6. When we create any extension like "citext" then we are not able to
>>>>> create the same extension again after deleting the same extension. May be
>>>>> issue
>>>>> with caching mechanism.
>>>>>
>>>> It is an architecture change. we'll fix it later.
>>>
>>>>
>>>>> 7. When we remove the schema_name during the "Edit" operation then
>>>>> wrong SQL is getting generated.
>>>>>
>>>> Fixed, Now it generates right SQL.
>>>
>>>>
>>>>>
>>>> 8. Remove "Use Slony" option. As discussed with Ashesh, we will
>>>>> implement it as separate module.
>>>>>
>>>> Removed.
>>>
>>>>
>>>>> Please fix the above issues. Let us know if you want more information.
>>>>>
>>>>> Thanks,
>>>>> Neel Patel
>>>>>
>>>>>
>>>>> Thanks,
>>>>> Neel Patel
>>>>>
>>>>>
>>>>> On Tue, Jan 12, 2016 at 1:15 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Please find the updated patch with following changes:
>>>>>>
>>>>>> 1. corrected copyright.
>>>>>> 2. Added proper comment for script_module function in __init__.py
>>>>>> file.
>>>>>> 3. Renamed collection Node's label to Extensions in extensions.js
>>>>>> file.
>>>>>>
>>>>>>
>>>>>> On Tue, Jan 12, 2016 at 12:44 PM, Surinder Kumar <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> Please find attached patch for the extension module.
>>>>>>> Please review it and Let me know for any comments.
>>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Surinder Kumar
>>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Sent via pgadmin-hackers mailing list ([email protected]
>>>>>> )
>>>>>> To make changes to your subscription:
>>>>>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers
Attachments:
[application/octet-stream] extension_v4.patch (29.6K, 3-extension_v4.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
new file mode 100644
index 0000000..37ad229
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/__init__.py
@@ -0,0 +1,394 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import 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
+
+# As unicode type is not available in python3
+# If we check a variable is "isinstance(variable, str)
+# it breaks in python 3 as variable type is not string its unicode.
+# We assign basestring as str type if it is python3, unicode
+# if it is python2.
+
+try:
+ unicode = unicode
+except NameError:
+ # 'unicode' is undefined, must be Python 3
+ str = str
+ unicode = str
+ bytes = bytes
+ basestring = (str,bytes)
+else:
+ # 'unicode' exists, must be Python 2
+ str = str
+ unicode = unicode
+ bytes = str
+ basestring = basestring
+
+
+class ExtensionModule(CollectionNodeModule):
+ NODE_TYPE = "extension"
+ COLLECTION_LABEL = gettext("Extensions")
+
+ def __init__(self, *args, **kwargs):
+ super(ExtensionModule, 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 extension, when any of the database node is
+ initialized.
+ """
+ return databases.DatabaseModule.NODE_TYPE
+
+
+blueprint = ExtensionModule(__name__)
+
+
+class ExtensionView(NodeView):
+ node_type = blueprint.node_type
+
+ parent_ids = [
+ {'type': 'int', 'id': 'gid'},
+ {'type': 'int', 'id': 'sid'},
+ {'type': 'int', 'id': 'did'}
+ ]
+ ids = [
+ {'type': 'int', 'id': 'eid'}
+ ]
+
+ operations = dict({
+ 'obj': [
+ {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+ {'get': 'list', 'post': 'create'}
+ ],
+ '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'}],
+ 'avails': [{}, {'get': 'avails'}],
+ 'schemas': [{}, {'get': 'schemas'}],
+ 'children': [{'get': 'children'}]
+ })
+
+ 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'])
+ self.template_path = 'extensions/sql'
+
+ return f(*args, **kwargs)
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did):
+ """
+ It fetches all extensions properties and render into properties
+ tab
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ 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):
+ """
+ It lists down the all extensions under the Extensions Collection node
+ """
+ res = []
+ SQL = render_template("/".join([self.template_path, 'properties.sql']))
+ 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['eid'],
+ row['name'],
+ 'icon-extension'
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, eid):
+ """
+ It fetches the properties of a single extension
+ and render in properties tab
+
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ 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 extension object
+ """
+ required_args = [
+ 'name'
+ ]
+
+ data = request.form if request.form else json.loads(request.data.decode())
+ for arg in required_args:
+ if arg not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=gettext(
+ "Couldn't find the required parameter (%s)." % arg
+ )
+ )
+
+ status, res = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'create.sql']),
+ data=data
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ status, rset = self.conn.execute_dict(
+ render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ ename=data['name']
+ )
+ )
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ for row in rset['rows']:
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ row['eid'],
+ row['name'],
+ 'icon-extension'
+ )
+ )
+
+ @check_precondition
+ def update(self, gid, sid, did, eid):
+ """
+ This function will update extension object
+ """
+ data = request.form if request.form else json.loads(request.data.decode())
+ SQL = self.getSQL(gid, sid, data, did, eid)
+
+ try:
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '):
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Extension updated",
+ data={
+ 'id': eid,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def delete(self, gid, sid, did, eid):
+ """
+ This function will delete drop/drop cascade the extension object
+ """
+ cascade = True if self.cmd == 'delete' else False
+ try:
+ # check if extension with eid exists
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), eid=eid)
+ status, name = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=name)
+ # drop extension
+ SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
+ status, res = self.conn.execute_scalar(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info=gettext("Extension dropped"),
+ data={
+ 'id': did,
+ 'sid': sid,
+ 'gid': gid,
+ }
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def msql(self, gid, sid, did, eid=None):
+ """
+ This function to return modified SQL
+ """
+ data = request.args.copy()
+ SQL = self.getSQL(gid, sid, data, did, eid)
+ if SQL and isinstance(SQL, basestring) and SQL.strip('\n') and SQL.strip(' '):
+ return make_json_response(
+ data=SQL,
+ status=200
+ )
+ else:
+ return make_json_response(
+ data='-- Modified SQL --',
+ status=200
+ )
+
+ def getSQL(self, gid, sid, data, did, eid=None):
+ """
+ This function will generate sql from model data
+ """
+ required_args = [
+ 'name'
+ ]
+ try:
+ if eid is not None:
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ old_data = res['rows'][0]
+ for arg in required_args:
+ if arg not in data:
+ data[arg] = old_data[arg]
+ SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
+ else:
+ SQL = render_template("/".join([self.template_path, 'create.sql']), data=data)
+ return SQL
+ except Exception as e:
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def avails(self, gid, sid, did):
+ """
+ This function with fetch all the available extensions
+ """
+ SQL = render_template("/".join([self.template_path, 'extensions.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def schemas(self, gid, sid, did):
+ """
+ This function with fetch all the schemas
+ """
+ SQL = render_template("/".join([self.template_path, 'schemas.sql']))
+ status, rset = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=rset)
+ return make_json_response(
+ data=rset['rows'],
+ status=200
+ )
+
+ 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(
+ "extensions/js/extensions.js",
+ _=gettext
+ ),
+ 200, {'Content-Type': 'application/x-javascript'}
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, eid):
+ """
+ This function will generate sql for sql panel
+ """
+ SQL = render_template("/".join([self.template_path, 'properties.sql']), eid=eid)
+ status, res = self.conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ result = res['rows'][0]
+
+ SQL = render_template("/".join([self.template_path, 'create.sql']),
+ data=result,
+ conn=self.conn,
+ display_comments=True
+ )
+
+ return ajax_response(response=SQL)
+
+ExtensionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/coll-extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz
literal 0
HcmV?d00001
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/extension.png b/web/pgadmin/browser/server_groups/servers/databases/extensions/static/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37
literal 0
HcmV?d00001
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
new file mode 100644
index 0000000..11385b0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/js/extensions.js
@@ -0,0 +1,196 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser) {
+
+ if (!pgBrowser.Nodes['coll-extension']) {
+ var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'extension',
+ label: '{{ _('Extension') }}',
+ type: 'coll-extension',
+ columns: ['name', 'owner', 'comment']
+ });
+ };
+
+ if (!pgBrowser.Nodes['extension']) {
+ pgAdmin.Browser.Nodes['extension'] =
+ pgAdmin.Browser.Node.extend({
+ parent_type: 'database',
+ type: 'extension',
+ hasSQL: true,
+ canDrop: true,
+ canDropCascade: true,
+ label: '{{ _('Extension') }}',
+
+ Init: function(){
+ if(this.initialized)
+ return;
+
+ this.initialized = true;
+
+ pgBrowser.add_menus([{
+ name: 'create_extension_on_coll', node: 'coll-extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ },{
+ name: 'create_extension', node: 'extension', module: this,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ category: 'create', priority: 4, label: '{{ _('Extension...') }}',
+ icon: 'wcTabIcon pg-icon-extension', data: {action: 'create'}
+ }
+ ]);
+ },
+ model: pgAdmin.Browser.Node.Model.extend({
+ schema: [
+ {
+ id: 'name', label: '{{ _('Name')}}', first_empty: true,
+ type: 'text', mode: ['properties', 'create', 'edit'],
+ visible: true, url:'avails', disabled: function(m) {
+ return !m.isNew();
+ },
+ transform: function(data) {
+ var res = [];
+ var label = this.model.get('name');
+ if (!this.model.isNew()){
+ res.push({label: label, value: label});
+ } else {
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ if (d.installed_version === null)
+ /* d contains json data and sets into
+ * select's option control
+ *
+ * We need to stringify data because formatter will
+ * convert Array Object as [Object] string
+ */
+ res.push({label: d.name, value: JSON.stringify(d)});
+ })
+ }
+ }
+ return res;
+ },
+ /* extends NodeAjaxOptionsControl to override the properties
+ * getValueFromDOM which takes stringified data from option of
+ * select control and parse it. And `onChange` takes the stringified
+ * data from select's option, thus convert it to json format and set the
+ * data into Model which is used to enable/disable the schema field.
+ */
+ control: Backform.NodeAjaxOptionsControl.extend({
+ getValueFromDOM: function() {
+ var data = this.formatter.toRaw(_.unescape(this.$el.find("select").val()), this.model);
+ /*
+ * return null if data is empty to prevent it from
+ * throwing parsing error. Adds check as name can be empty
+ */
+ if (data === ''){
+ return null;
+ }
+ else if (typeof(data) === 'string') {
+ data=JSON.parse(data);
+ }
+ return data.name;
+ },
+ onChange: function() {
+ Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
+ var selectedValue = this.$el.find("select").val();
+ if (selectedValue.trim() != ""){
+ var d = this.formatter.toRaw(selectedValue, this.model);
+ if(typeof(d) === 'string')
+ d=JSON.parse(d);
+ var changes = {
+ 'version' : '',
+ 'relocatable': ((!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''),
+ 'schema': ((!_.isNull(d.schema[0]) && !_.isUndefined(d.schema[0])) ? d.schema[0]: '')
+ };
+ this.model.set(changes);
+ }else{
+ var changes = {'version': '', 'relocatable': true, 'schema': ''};
+ this.model.set(changes);
+ }
+ },
+ })
+ },{
+ id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
+ type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
+ },{
+ id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
+ mode: ['properties'], node: 'role', cell: 'string'
+ },{
+ id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
+ mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
+ url: 'schemas', first_empty: true, disabled: function(m) {
+ /* enable or disable schema field if model's relocatable
+ * attribute is True or False
+ */
+ return (m.has('relocatable') ? !m.get('relocatable') : false);
+ },
+ transform: function(data) {
+ var res = [];
+ if (data && _.isArray(data)) {
+ _.each(data, function(d) {
+ res.push({label: d.schema, value: d.schema});
+ })
+ }
+ return res;
+ }
+ },
+ {
+ id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
+ type: 'switch', mode: ['properties'], 'options': {
+ 'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
+ 'offColor': 'default', 'size': 'small'
+ }
+ },
+ {
+ id: 'version', label: '{{ _('Version')}}', cell: 'string',
+ mode: ['properties', 'create', 'edit'], group: 'Definition',
+ control: 'node-ajax-options', url:'avails', first_empty: true,
+ /*
+ * Transform the data into version for the selected extension.
+ */
+ transform: function(data) {
+ res = [];
+ var extension = this.model.get('name');
+ _.each(data, function(dt){
+ if(dt.name == extension){
+ if(dt.version && _.isArray(dt.version)){
+ _.each(dt.version, function(v){
+ res.push({ label: v, value: v });
+ });
+ }
+ }
+ });
+ return res;
+ }
+ },
+ {
+ id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
+ type: 'multiline', disabled: true
+ }
+ ],
+ validate: function() {
+ /*
+ * Triggers error messages for name
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ name = this.get('name');
+ if (_.isUndefined(name) || _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ err['name'] = '{{ _('Name can not be empty!') }}';
+ errmsg = errmsg || err['name'];
+ this.errorModel.set('name', errmsg);
+ return errmsg;
+ }else{
+ this.errorModel.unset('name');
+ }
+ return null;
+ }
+ })
+ })
+ };
+
+ return pgBrowser.Nodes['coll-extension'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
new file mode 100644
index 0000000..288a7cc
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/create.sql
@@ -0,0 +1,19 @@
+{#=========================Create new extension======================#}
+{#===Generates comments and code for SQL tab===#}
+{% if display_comments %}
+-- Extension: {{ conn|qtIdent(data.name) }}
+
+-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{% if data.name %}
+ CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
+{% if data.schema %}
+
+ SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
+{% endif %}
+{% if data.version %}
+
+ VERSION {{ conn|qtIdent(data.version) }};
+{% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
new file mode 100644
index 0000000..44155f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/delete.sql
@@ -0,0 +1,8 @@
+{#============================Drop/Cascade Extension by name=========================#}
+{% if eid %}
+SELECT x.extname from pg_extension x
+ WHERE x.oid = {{ eid }}::int
+{% endif %}
+{% if name %}
+DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
new file mode 100644
index 0000000..bf3979d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/extensions.sql
@@ -0,0 +1,12 @@
+{# ======================Fetch extensions names=====================#}
+SELECT
+ a.name, a.installed_version,
+ array_agg(av.version) as version,
+ array_agg(av.schema) as schema,
+ array_agg(av.superuser) as superuser,
+ array_agg(av.relocatable) as relocatable
+FROM
+ pg_available_extensions a
+ LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
+GROUP BY a.name, a.installed_version
+ORDER BY a.name
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
new file mode 100644
index 0000000..f652676
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/properties.sql
@@ -0,0 +1,17 @@
+{#===================Fetch properties of each extension by name or oid===================#}
+SELECT
+ x.oid AS eid, pg_get_userbyid(extowner) AS owner,
+ x.extname AS name, n.nspname AS schema,
+ x.extrelocatable AS relocatable, x.extversion AS version,
+ e.comment
+FROM
+ pg_extension x
+ LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
+ JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
+{%- if eid %}
+ WHERE x.oid = {{eid}}::int
+{% elif ename %}
+ WHERE x.extname = {{ename|qtLiteral}}::text
+{% else %}
+ ORDER BY x.extname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
new file mode 100644
index 0000000..b8e3c97
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/schemas.sql
@@ -0,0 +1,3 @@
+{#===================fetch all schemas==========================#}
+SELECT nspname As schema FROM pg_namespace
+ ORDER BY nspname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
new file mode 100644
index 0000000..c4cd626
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/extensions/templates/extensions/sql/update.sql
@@ -0,0 +1,10 @@
+{# =============Update extension schema============= #}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ SET SCHEMA {{ conn|qtIdent(data.schema) }};
+{% endif %}
+{# =============Update extension version============= #}
+{% if data.version and data.version != o_data.version %}
+ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
+ UPDATE TO {{ conn|qtIdent(data.version) }};
+{% endif %}
view thread (13+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: [pgAdmin4] [Patch]: Extension Module
In-Reply-To: <CAM5-9D-FMDcV8L1FbPiEU4pCeOO1dcLu-62r5CHRuO_ks9hiSw@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox