codekingpro/portable-devtools
114k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10""" Implements Extension Node """11 12import json13from functools import wraps14 15from pgadmin.browser.server_groups.servers import databases16from flask import render_template, request, jsonify17from flask_babel import gettext18from pgadmin.browser.collection import CollectionNodeModule19from pgadmin.browser.utils import PGChildNodeView20from pgadmin.utils.ajax import make_json_response, \21 make_response as ajax_response, internal_server_error, gone22from pgadmin.utils.driver import get_driver23from pgadmin.browser.server_groups.servers.databases.extensions.utils \24 import get_extension_details25from config import PG_DEFAULT_DRIVER26from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry27from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare28 29 30class ExtensionModule(CollectionNodeModule):31 """32 class ExtensionModule():33 34 A collection Node which inherits CollectionNodeModule35 class and define methods to get child nodes, to load its own36 javascript file.37 """38 _NODE_TYPE = "extension"39 _COLLECTION_LABEL = gettext("Extensions")40 41 def __init__(self, *args, **kwargs):42 """43 Initialising the base class44 """45 super().__init__(*args, **kwargs)46 47 def get_nodes(self, gid, sid, did):48 """49 Generate the collection node50 """51 if self.has_nodes(sid, did,52 base_template_path=ExtensionView.EXT_TEMPLATE_PATH):53 yield self.generate_browser_collection_node(did)54 55 @property56 def node_inode(self):57 """58 If a node have child return True otherwise False59 """60 return False61 62 @property63 def script_load(self):64 """65 Load the module script for extension, when any of the database nodes66 are initialized.67 """68 return databases.DatabaseModule.node_type69 70 @property71 def module_use_template_javascript(self):72 """73 Returns whether Jinja2 template is used for generating the javascript74 module.75 """76 return False77 78 79# Create blueprint of extension module80blueprint = ExtensionModule(__name__)81 82 83class ExtensionView(PGChildNodeView, SchemaDiffObjectCompare):84 """85 This is a class for extension nodes which inherits the86 properties and methods from NodeView class and define87 various methods to list, create, update and delete extension.88 89 Variables:90 ---------91 * node_type - tells which type of node it is92 * parent_ids - id with its type and name of parent nodes93 * ids - id with type and name of extension module being used.94 * operations - function routes mappings defined.95 """96 EXT_TEMPLATE_PATH = 'extensions/sql'97 node_type = blueprint.node_type98 99 parent_ids = [100 {'type': 'int', 'id': 'gid'},101 {'type': 'int', 'id': 'sid'},102 {'type': 'int', 'id': 'did'}103 ]104 ids = [105 {'type': 'int', 'id': 'eid'}106 ]107 108 operations = dict({109 'obj': [110 {'get': 'properties', 'delete': 'delete', 'put': 'update'},111 {'get': 'list', 'post': 'create', 'delete': 'delete'}112 ],113 'delete': [{'delete': 'delete'}, {'delete': 'delete'}],114 'nodes': [{'get': 'node'}, {'get': 'nodes'}],115 'sql': [{'get': 'sql'}],116 'msql': [{'get': 'msql'}, {'get': 'msql'}],117 'stats': [{'get': 'statistics'}],118 'dependency': [{'get': 'dependencies'}],119 'dependent': [{'get': 'dependents'}],120 'avails': [{}, {'get': 'avails'}],121 'schemas': [{}, {'get': 'schemas'}],122 'children': [{'get': 'children'}]123 })124 125 keys_to_ignore = ['oid', 'oid-2', 'owner']126 127 def check_precondition(f):128 """129 This function will behave as a decorator which will checks130 database connection before running view, it will also attaches131 manager,conn & template_path properties to self132 """133 134 @wraps(f)135 def wrap(*args, **kwargs):136 # Here args[0] will hold self & kwargs will hold gid,sid,did137 self = args[0]138 self.manager = get_driver(139 PG_DEFAULT_DRIVER140 ).connection_manager(kwargs['sid'])141 self.conn = self.manager.connection(did=kwargs['did'])142 self.template_path = self.EXT_TEMPLATE_PATH143 144 self.datistemplate = False145 if (146 self.manager.db_info is not None and147 kwargs['did'] in self.manager.db_info and148 'datistemplate' in self.manager.db_info[kwargs['did']]149 ):150 self.datistemplate = self.manager.db_info[151 kwargs['did']]['datistemplate']152 153 return f(*args, **kwargs)154 155 return wrap156 157 @check_precondition158 def list(self, gid, sid, did):159 """160 Fetches all extensions properties and render into properties tab161 """162 SQL = render_template("/".join([self.template_path,163 self._PROPERTIES_SQL]),164 conn=self.conn)165 status, res = self.conn.execute_dict(SQL)166 167 if not status:168 return internal_server_error(errormsg=res)169 return ajax_response(170 response=res['rows'],171 status=200172 )173 174 @check_precondition175 def nodes(self, gid, sid, did):176 """177 Lists all extensions under the Extensions Collection node178 """179 res = []180 SQL = render_template("/".join([self.template_path,181 self._PROPERTIES_SQL]),182 conn=self.conn)183 status, rset = self.conn.execute_2darray(SQL)184 if not status:185 return internal_server_error(errormsg=rset)186 187 for row in rset['rows']:188 res.append(189 self.blueprint.generate_browser_node(190 row['oid'],191 did,192 row['name'],193 'icon-extension',194 description=row['comment']195 ))196 197 return make_json_response(198 data=res,199 status=200200 )201 202 @check_precondition203 def node(self, gid, sid, did, eid):204 """205 This function will fetch the properties of extension206 """207 SQL = render_template("/".join([self.template_path,208 self._PROPERTIES_SQL]),209 eid=eid, conn=self.conn)210 status, rset = self.conn.execute_2darray(SQL)211 if not status:212 return internal_server_error(errormsg=rset)213 214 for row in rset['rows']:215 return make_json_response(216 data=self.blueprint.generate_browser_node(217 row['oid'],218 did,219 row['name'],220 'icon-extension'221 ),222 status=200223 )224 225 return gone(gettext("Could not find the specified extension."))226 227 @check_precondition228 def properties(self, gid, sid, did, eid):229 """230 Fetch the properties of a single extension and render in properties tab231 """232 status, res = self._fetch_properties(did, eid)233 if not status:234 return res235 236 return ajax_response(237 response=res,238 status=200239 )240 241 def _fetch_properties(self, did, eid):242 """243 This function fetch the properties of the extension.244 :param did:245 :param eid:246 :return:247 """248 SQL = render_template("/".join(249 [self.template_path, self._PROPERTIES_SQL]), eid=eid,250 conn=self.conn)251 status, res = self.conn.execute_dict(SQL)252 if not status:253 return False, internal_server_error(errormsg=res)254 255 if len(res['rows']) == 0:256 return False, gone(257 gettext("Could not find the extension information.")258 )259 260 res['rows'][0]['is_sys_obj'] = (261 res['rows'][0]['oid'] <= self._DATABASE_LAST_SYSTEM_OID or262 self.datistemplate)263 264 return True, res['rows'][0]265 266 @check_precondition267 def create(self, gid, sid, did):268 """269 Create a new extension object270 """271 required_args = [272 'name'273 ]274 275 data = request.form if request.form else json.loads(276 request.data277 )278 279 for arg in required_args:280 if arg not in data:281 return make_json_response(282 status=410,283 success=0,284 errormsg=gettext(285 "Could not find the required parameter ({})."286 ).format(arg)287 )288 289 status, res = self.conn.execute_dict(290 render_template(291 "/".join([self.template_path, self._CREATE_SQL]),292 data=data293 )294 )295 296 if not status:297 return internal_server_error(errormsg=res)298 299 status, res = get_extension_details(300 self.conn, data['name'],301 "/".join([self.template_path, self._PROPERTIES_SQL]))302 if not status:303 return internal_server_error(errormsg=res)304 305 return jsonify(306 node=self.blueprint.generate_browser_node(307 res['oid'],308 did,309 res['name'],310 'icon-extension'311 )312 )313 314 @check_precondition315 def update(self, gid, sid, did, eid):316 """317 This function will update an extension object318 """319 data = request.form if request.form else json.loads(320 request.data321 )322 323 try:324 SQL, name = self.getSQL(gid, sid, data, did, eid)325 # Most probably this is due to error326 if not isinstance(SQL, str):327 return SQL328 SQL = SQL.strip('\n').strip(' ')329 status, res = self.conn.execute_dict(SQL)330 if not status:331 return internal_server_error(errormsg=res)332 333 other_node_info = {}334 if 'comment' in data:335 other_node_info['description'] = data['comment']336 337 return jsonify(338 node=self.blueprint.generate_browser_node(339 eid,340 did,341 name,342 icon="icon-%s" % self.node_type,343 **other_node_info344 )345 )346 except Exception as e:347 return internal_server_error(errormsg=str(e))348 349 @check_precondition350 def delete(self, gid, sid, did, eid=None, only_sql=False):351 """352 This function will drop/drop cascade a extension object353 """354 355 if eid is None:356 data = request.form if request.form else json.loads(357 request.data358 )359 else:360 data = {'ids': [eid]}361 362 cascade = self._check_cascade_operation()363 364 try:365 for eid in data['ids']:366 # check if extension with eid exists367 SQL = render_template("/".join(368 [self.template_path, self._DELETE_SQL]), eid=eid)369 status, name = self.conn.execute_scalar(SQL)370 if not status:371 return internal_server_error(errormsg=name)372 373 if name is None:374 return make_json_response(375 status=410,376 success=0,377 errormsg=gettext(378 'Error: Object not found.'379 ),380 info=gettext(381 'The specified extension could not be found.\n'382 )383 )384 385 # drop extension386 SQL = render_template("/".join(387 [self.template_path, self._DELETE_SQL]388 ), name=name, cascade=cascade)389 390 # Used for schema diff tool391 if only_sql:392 return SQL393 394 status, res = self.conn.execute_scalar(SQL)395 if not status:396 return internal_server_error(errormsg=res)397 398 return make_json_response(399 success=1,400 info=gettext("Extension dropped")401 )402 except Exception as e:403 return internal_server_error(errormsg=str(e))404 405 @check_precondition406 def msql(self, gid, sid, did, eid=None):407 """408 This function returns modified SQL409 """410 data = {}411 for k, v in request.args.items():412 try:413 data[k] = json.loads(v)414 except ValueError:415 data[k] = v416 417 try:418 SQL, _ = self.getSQL(gid, sid, data, did, eid)419 # Most probably this is due to error420 if not isinstance(SQL, str):421 return SQL422 SQL = SQL.strip('\n').strip(' ')423 if SQL == '':424 SQL = "--modified SQL"425 426 return make_json_response(427 data=SQL,428 status=200429 )430 except Exception as e:431 return internal_server_error(errormsg=str(e))432 433 def getSQL(self, gid, sid, data, did, eid=None):434 """435 This function will generate sql from model data436 """437 required_args = [438 'name'439 ]440 441 if eid is not None:442 SQL = render_template("/".join(443 [self.template_path, self._PROPERTIES_SQL]444 ), eid=eid, conn=self.conn)445 status, res = self.conn.execute_dict(SQL)446 if not status:447 return internal_server_error(errormsg=res)448 449 if len(res['rows']) == 0:450 return gone(451 gettext("Could not find the extension information.")452 )453 454 old_data = res['rows'][0]455 for arg in required_args:456 if arg not in data:457 data[arg] = old_data[arg]458 SQL = render_template("/".join(459 [self.template_path, self._UPDATE_SQL]460 ), data=data, o_data=old_data)461 return SQL, data['name'] if 'name' in data else old_data['name']462 else:463 SQL = render_template("/".join(464 [self.template_path, self._CREATE_SQL]465 ), data=data)466 return SQL, data['name']467 468 @check_precondition469 def avails(self, gid, sid, did):470 """471 This function with fetch all the available extensions472 """473 SQL = render_template("/".join([self.template_path, 'extensions.sql']))474 status, rset = self.conn.execute_dict(SQL)475 if not status:476 return internal_server_error(errormsg=rset)477 return make_json_response(478 data=rset['rows'],479 status=200480 )481 482 @check_precondition483 def schemas(self, gid, sid, did):484 """485 This function with fetch all the schemas486 """487 SQL = render_template("/".join([self.template_path, 'schemas.sql']))488 status, rset = self.conn.execute_dict(SQL)489 if not status:490 return internal_server_error(errormsg=rset)491 return make_json_response(492 data=rset['rows'],493 status=200494 )495 496 @check_precondition497 def sql(self, gid, sid, did, eid, json_resp=True):498 """499 This function will generate sql for the sql panel500 """501 SQL = render_template("/".join(502 [self.template_path, self._PROPERTIES_SQL]503 ), eid=eid, conn=self.conn)504 status, res = self.conn.execute_dict(SQL)505 if not status:506 return internal_server_error(errormsg=res)507 if len(res['rows']) == 0:508 return gone(509 gettext("Could not find the extension on the server.")510 )511 512 result = res['rows'][0]513 514 SQL = render_template("/".join(515 [self.template_path, self._CREATE_SQL]516 ),517 data=result,518 conn=self.conn,519 display_comments=True,520 add_not_exists_clause=True521 )522 523 if not json_resp:524 return SQL525 526 return ajax_response(response=SQL)527 528 @check_precondition529 def dependents(self, gid, sid, did, eid):530 """531 This function gets the dependents and returns an ajax response532 for the extension node.533 534 Args:535 gid: Server Group ID536 sid: Server ID537 did: Database ID538 eid: Extension ID539 """540 dependents_result = self.get_dependents(self.conn, eid)541 return ajax_response(542 response=dependents_result,543 status=200544 )545 546 @check_precondition547 def dependencies(self, gid, sid, did, eid):548 """549 This function gets the dependencies and returns an ajax response550 for the extension node.551 552 Args:553 gid: Server Group ID554 sid: Server ID555 did: Database ID556 lid: Extension ID557 """558 dependencies_result = self.get_dependencies(self.conn, eid)559 return ajax_response(560 response=dependencies_result,561 status=200562 )563 564 @check_precondition565 def fetch_objects_to_compare(self, sid, did):566 """567 This function will fetch the list of all the extensions for568 specified database id.569 570 :param sid: Server Id571 :param did: Database Id572 :return:573 """574 res = dict()575 576 sql = render_template("/".join([self.template_path,577 self._PROPERTIES_SQL]),578 conn=self.conn)579 status, rset = self.conn.execute_2darray(sql)580 if not status:581 return internal_server_error(errormsg=rset)582 583 for row in rset['rows']:584 status, data = self._fetch_properties(did, row['oid'])585 if status:586 res[row['name']] = data587 588 return res589 590 def get_sql_from_diff(self, **kwargs):591 """592 This function is used to get the DDL/DML statements.593 :param kwargs594 :return:595 """596 gid = kwargs.get('gid')597 sid = kwargs.get('sid')598 did = kwargs.get('did')599 oid = kwargs.get('oid')600 data = kwargs.get('data', None)601 drop_sql = kwargs.get('drop_sql', False)602 603 if data:604 sql, _ = self.getSQL(gid=gid, sid=sid, did=did, data=data,605 eid=oid)606 else:607 if drop_sql:608 sql = self.delete(gid=gid, sid=sid, did=did,609 eid=oid, only_sql=True)610 else:611 sql = self.sql(gid=gid, sid=sid, did=did, eid=oid,612 json_resp=False)613 return sql614 615 616SchemaDiffRegistry(blueprint.node_type, ExtensionView, 'Database')617# Register and add ExtensionView as blueprint618ExtensionView.register_node_view(blueprint)619 