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 the Database Node"""11 12import re13from functools import wraps14 15import json16from flask import render_template, current_app, request, jsonify17from flask_babel import gettext as _18from flask_security import current_user19 20from pgadmin.browser.server_groups import servers21from config import PG_DEFAULT_DRIVER22from pgadmin.browser.collection import CollectionNodeModule23from pgadmin.browser.server_groups.servers.databases.utils import \24 parse_sec_labels_from_db, parse_variables_from_db, \25 get_attributes_from_db_info26from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \27 parse_priv_to_db28from pgadmin.browser.utils import PGChildNodeView29from pgadmin.utils.ajax import gone30from pgadmin.utils.ajax import make_json_response, \31 make_response as ajax_response, internal_server_error, unauthorized32from pgadmin.utils.driver import get_driver33from pgadmin.tools.sqleditor.utils.query_history import QueryHistory34 35from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry36from pgadmin.model import db, Server, Database37from pgadmin.browser.utils import underscore_escape38 39 40class DatabaseModule(CollectionNodeModule):41 _NODE_TYPE = 'database'42 _COLLECTION_LABEL = _("Databases")43 44 _DATABASE_CSS_PATH = 'databases/css'45 _DATABASE_CSS = "/".join([_DATABASE_CSS_PATH, 'database.css'])46 47 def __init__(self, *args, **kwargs):48 self.min_ver = None49 self.max_ver = None50 51 super().__init__(*args, **kwargs)52 53 def get_nodes(self, gid, sid):54 """55 Generate the collection node56 """57 if self.show_node:58 yield self.generate_browser_collection_node(sid)59 60 @property61 def script_load(self):62 """63 Load the module script for server, when any of the server-group node is64 initialized.65 """66 return servers.ServerModule.node_type67 68 @property69 def csssnippets(self):70 """71 Returns a snippet of css to include in the page72 """73 snippets = [74 render_template(75 self._COLLECTION_CSS,76 node_type=self.node_type,77 _=_78 ),79 render_template(80 self._DATABASE_CSS,81 node_type=self.node_type,82 _=_83 )84 ]85 86 for submodule in self.submodules:87 snippets.extend(submodule.csssnippets)88 89 return snippets90 91 @property92 def module_use_template_javascript(self):93 """94 Returns whether Jinja2 template is used for generating the javascript95 module.96 """97 return False98 99 def register(self, app, options):100 """101 Override the default register function to automagically register102 sub-modules at once.103 """104 from .casts import blueprint as module105 self.submodules.append(module)106 107 from .event_triggers import blueprint as module108 self.submodules.append(module)109 110 from .extensions import blueprint as module111 self.submodules.append(module)112 113 from .foreign_data_wrappers import blueprint as module114 self.submodules.append(module)115 116 from .languages import blueprint as module117 self.submodules.append(module)118 119 from .publications import blueprint as module120 self.submodules.append(module)121 122 from .schemas import schema_blueprint as module123 self.submodules.append(module)124 125 from .schemas import catalog_blueprint as module126 self.submodules.append(module)127 128 from .subscriptions import blueprint as module129 self.submodules.append(module)130 131 from .dbms_job_scheduler import blueprint as module132 self.submodules.append(module)133 134 super().register(app, options)135 136 137blueprint = DatabaseModule(__name__)138 139 140class DatabaseView(PGChildNodeView):141 node_type = blueprint.node_type142 node_label = "Database"143 144 parent_ids = [145 {'type': 'int', 'id': 'gid'},146 {'type': 'int', 'id': 'sid'}147 ]148 ids = [149 {'type': 'int', 'id': 'did'}150 ]151 152 operations = dict({153 'obj': [154 {'get': 'properties', 'delete': 'delete', 'put': 'update'},155 {'get': 'list', 'post': 'create', 'delete': 'delete'}156 ],157 'nodes': [158 {'get': 'node'},159 {'get': 'nodes'}160 ],161 'get_databases': [162 {'get': 'get_databases'},163 {'get': 'get_databases'}164 ],165 'sql': [166 {'get': 'sql'}167 ],168 'msql': [169 {'get': 'msql'},170 {'get': 'msql'}171 ],172 'stats': [173 {'get': 'statistics'},174 {'get': 'statistics'}175 ],176 'dependency': [177 {'get': 'dependencies'}178 ],179 'dependent': [180 {'get': 'dependents'}181 ],182 'children': [183 {'get': 'children'}184 ],185 'connect': [186 {187 'get': 'connect_status',188 'post': 'connect',189 'delete': 'disconnect'190 }191 ],192 'get_encodings': [193 {'get': 'get_encodings'},194 {'get': 'get_encodings'}195 ],196 'get_ctypes': [197 {'get': 'get_ctypes'},198 {'get': 'get_ctypes'}199 ],200 'get_icu_locale': [201 {'get': 'get_icu_locale'},202 {'get': 'get_icu_locale'}203 ],204 'vopts': [205 {}, {'get': 'variable_options'}206 ],207 'delete': [{'delete': 'delete'},208 {'delete': 'delete'}]209 })210 211 def check_precondition(action=None):212 """213 This function will behave as a decorator which will checks214 database connection before running view, it will also attaches215 manager,conn & template_path properties to self216 """217 218 def wrap(f):219 @wraps(f)220 def wrapped(self, *args, **kwargs):221 self.manager = get_driver(222 PG_DEFAULT_DRIVER223 ).connection_manager(224 kwargs['sid']225 )226 if self.manager is None:227 return gone(errormsg=_("Could not find the server."))228 229 self.datistemplate = False230 if action and action in ["drop"]:231 self.conn = self.manager.connection()232 elif 'did' in kwargs:233 self.conn = self.manager.connection(did=kwargs['did'])234 self.db_allow_connection = True235 # If connection to database is not allowed then236 # provide generic connection237 if kwargs['did'] in self.manager.db_info:238 self._db = self.manager.db_info[kwargs['did']]239 self.datistemplate, datallowconn = \240 get_attributes_from_db_info(self.manager, kwargs)241 242 if datallowconn is False:243 self.conn = self.manager.connection()244 self.db_allow_connection = False245 246 else:247 self.conn = self.manager.connection()248 249 # set template path for sql scripts250 self.template_path = 'databases/sql/#{0}#'.format(251 self.manager.version252 )253 254 return f(self, *args, **kwargs)255 256 return wrapped257 258 return wrap259 260 @check_precondition(action="list")261 def list(self, gid, sid):262 last_system_oid = self.retrieve_last_system_oid()263 264 db_disp_res = None265 params = None266 if self.manager and self.manager.db_res:267 db_disp_res = ", ".join(268 ['%s'] * len(self.manager.db_res.split(','))269 )270 params = tuple(self.manager.db_res.split(','))271 272 SQL = render_template(273 "/".join([self.template_path, self._PROPERTIES_SQL]),274 conn=self.conn,275 last_system_oid=last_system_oid,276 db_restrictions=db_disp_res,277 )278 status, res = self.conn.execute_dict(SQL, params)279 280 if not status:281 return internal_server_error(errormsg=res)282 283 result_set = []284 for row in res['rows']:285 row['is_sys_obj'] = (286 row['did'] <= self._DATABASE_LAST_SYSTEM_OID or287 self.datistemplate)288 if self.skip_db(row):289 continue290 291 if self.manager.db == row['name']:292 row['canDrop'] = False293 else:294 row['canDrop'] = True295 296 result_set.append(row)297 298 return ajax_response(299 response=result_set,300 status=200301 )302 303 def retrieve_last_system_oid(self):304 last_system_oid = 0305 306 if not self.blueprint.show_system_objects:307 last_system_oid = self._DATABASE_LAST_SYSTEM_OID308 309 return last_system_oid310 311 def get_icon(self, res, connected):312 if not connected and not res['is_template']:313 icon = "icon-database-not-connected"314 elif not connected and res['is_template']:315 icon = 'icon-database-template-not-connected'316 elif connected and res['is_template']:317 icon = 'icon-database-template-connected'318 else:319 icon = "pg-icon-database"320 321 return icon322 323 def skip_db(self, row):324 325 if not self.blueprint.show_system_objects \326 and row['is_sys_obj'] \327 and row['name'] not in ('postgres', 'edb') \328 or not self.blueprint.show_database_template \329 and row['is_sys_obj'] \330 and row['name'] not in ('postgres', 'edb'):331 return True332 333 if not self.blueprint.show_database_template \334 and row['is_template'] and \335 not row['is_sys_obj'] and \336 row['name'] not in ('postgres', 'edb'):337 return True338 return False339 340 def get_nodes(self, gid, sid, is_schema_diff=False):341 res = []342 last_system_oid = self.retrieve_last_system_oid()343 344 # if is_schema_diff then no need to show system templates.345 if is_schema_diff and self.manager.db_info is not None and \346 self.manager.did in self.manager.db_info:347 last_system_oid = self._DATABASE_LAST_SYSTEM_OID348 349 server_node_res = self.manager350 351 db_disp_res = None352 params = None353 if server_node_res and server_node_res.db_res:354 db_disp_res = ", ".join(355 ['%s'] * len(server_node_res.db_res.split(','))356 )357 params = tuple(server_node_res.db_res.split(','))358 SQL = render_template(359 "/".join([self.template_path, self._NODES_SQL]),360 last_system_oid=last_system_oid,361 db_restrictions=db_disp_res,362 )363 status, rset = self.conn.execute_dict(SQL, params)364 365 if not status:366 return internal_server_error(errormsg=rset)367 368 for row in rset['rows']:369 dbname = row['name']370 row['is_sys_obj'] = (371 row['did'] <= self._DATABASE_LAST_SYSTEM_OID or372 self.datistemplate)373 374 if self.skip_db(row):375 continue376 if self.manager.db == dbname:377 connected = True378 can_drop = can_dis_conn = False379 else:380 conn = self.manager.connection(database=dbname, did=row['did'])381 connected = conn.connected()382 can_drop = can_dis_conn = True383 384 icon = self.get_icon(row, connected)385 386 res.append(387 self.blueprint.generate_browser_node(388 row['did'],389 sid,390 row['name'],391 icon=icon,392 connected=connected,393 tablespace=row['spcname'],394 allowConn=row['datallowconn'],395 canCreate=row['cancreate'],396 canDisconn=can_dis_conn,397 canDrop=can_drop,398 isTemplate=row['is_template'],399 inode=True if row['datallowconn'] else False,400 description=row['description']401 )402 )403 404 return res405 406 @check_precondition(action="nodes")407 def nodes(self, gid, sid, is_schema_diff=False):408 res = self.get_nodes(gid, sid, is_schema_diff)409 410 return make_json_response(411 data=res,412 status=200413 )414 415 @check_precondition(action="get_databases")416 def get_databases(self, gid, sid):417 """418 This function is used to get all the databases irrespective of419 show_system_object flag for templates in create database dialog.420 :param gid:421 :param sid:422 :return:423 """424 res = []425 SQL = render_template(426 "/".join([self.template_path, self._NODES_SQL]),427 last_system_oid=0,428 show_system_objects=True,429 )430 status, rset = self.conn.execute_dict(SQL)431 432 if not status:433 return internal_server_error(errormsg=rset)434 435 for row in rset['rows']:436 res.append(row['name'])437 438 return make_json_response(439 data=res,440 status=200441 )442 443 @check_precondition(action="node")444 def node(self, gid, sid, did):445 SQL = render_template(446 "/".join([self.template_path, self._NODES_SQL]),447 did=did, conn=self.conn, last_system_oid=0,448 show_system_objects=self.blueprint.show_system_objects,449 )450 status, rset = self.conn.execute_2darray(SQL)451 452 if not status:453 return internal_server_error(errormsg=rset)454 455 for row in rset['rows']:456 db = row['name']457 if self.manager.db == db:458 connected = True459 else:460 conn = self.manager.connection(database=row['name'])461 connected = conn.connected()462 icon_css_class = "pg-icon-database"463 if not connected:464 icon_css_class = "icon-database-not-connected"465 return make_json_response(466 data=self.blueprint.generate_browser_node(467 row['did'],468 sid,469 row['name'],470 icon=icon_css_class,471 connected=connected,472 spcname=row['spcname'],473 allowConn=row['datallowconn'],474 canCreate=row['cancreate']475 ),476 status=200477 )478 479 return gone(errormsg=self.not_found_error_msg())480 481 @check_precondition(action="properties")482 def properties(self, gid, sid, did):483 484 SQL = render_template(485 "/".join([self.template_path, self._PROPERTIES_SQL]),486 did=did, conn=self.conn, last_system_oid=0,487 show_system_objects=self.blueprint.show_system_objects488 )489 status, res = self.conn.execute_dict(SQL)490 491 if not status:492 return internal_server_error(errormsg=res)493 494 if len(res['rows']) == 0:495 return gone(496 self.not_found_error_msg()497 )498 499 SQL = render_template(500 "/".join([self.template_path, self._ACL_SQL]),501 did=did, conn=self.conn502 )503 status, dataclres = self.conn.execute_dict(SQL)504 if not status:505 return internal_server_error(errormsg=res)506 507 res = self.formatdbacl(res, dataclres['rows'])508 509 SQL = render_template(510 "/".join([self.template_path, 'defacl.sql']),511 did=did, conn=self.conn, grant_reovke_sql=False512 )513 status, defaclres = self.conn.execute_dict(SQL)514 if not status:515 return internal_server_error(errormsg=res)516 517 res = self.formatdbacl(res, defaclres['rows'])518 519 result = res['rows'][0]520 result['is_sys_obj'] = (521 result['oid'] <= self._DATABASE_LAST_SYSTEM_OID)522 # Fetching variable for database523 SQL = render_template(524 "/".join([self.template_path, 'get_variables.sql']),525 did=did, conn=self.conn526 )527 528 status, res1 = self.conn.execute_dict(SQL)529 database = Database.query.filter_by(id=did, server=sid).first()530 531 if database:532 result['schema_res'] = database.schema_res.split(533 ',') if database.schema_res else []534 535 if not status:536 return internal_server_error(errormsg=res1)537 538 # Get Formatted Security Labels539 if 'seclabels' in result:540 # Security Labels is not available for PostgreSQL <= 9.1541 frmtd_sec_labels = parse_sec_labels_from_db(result['seclabels'])542 result.update(frmtd_sec_labels)543 544 # Get Formatted Variables545 frmtd_variables = parse_variables_from_db(res1['rows'])546 result.update(frmtd_variables)547 548 return ajax_response(549 response=result,550 status=200551 )552 553 @staticmethod554 def formatdbacl(res, dbacl):555 for row in dbacl:556 priv = parse_priv_from_db(row)557 res['rows'][0].setdefault(row['deftype'], []).append(priv)558 return res559 560 def connect(self, gid, sid, did):561 """Connect the Database."""562 from pgadmin.utils.driver import get_driver563 manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)564 conn = manager.connection(did=did, auto_reconnect=True)565 already_connected = conn.connected()566 if not already_connected:567 status, errmsg = conn.connect()568 if not status:569 current_app.logger.error(570 "Could not connected to database(#{0}).\nError: {1}"571 .format(572 did, errmsg573 )574 )575 576 return internal_server_error(errmsg)577 else:578 current_app.logger.info(579 'Connection Established for Database Id: \580 %s' % (did)581 )582 return make_json_response(583 success=1,584 info=_("Database connected."),585 data={586 'icon': 'pg-icon-database',587 'already_connected': already_connected,588 'connected': True,589 'info_prefix': '{0}/{1}'.590 format(Server.query.filter_by(id=sid)[0].name, conn.db)591 }592 )593 594 def disconnect(self, gid, sid, did):595 """Disconnect the database."""596 597 # Release Connection598 from pgadmin.utils.driver import get_driver599 manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)600 conn = manager.connection(did=did, auto_reconnect=True)601 status = manager.release(did=did)602 603 if not status:604 return unauthorized(_("Database could not be disconnected."))605 else:606 return make_json_response(607 success=1,608 info=_("Database disconnected."),609 data={610 'icon': 'icon-database-not-connected',611 'connected': False,612 'info_prefix': '{0}/{1}'.613 format(Server.query.filter_by(id=sid)[0].name, conn.db)614 }615 )616 617 @check_precondition(action="get_encodings")618 def get_encodings(self, gid, sid, did=None):619 """620 This function to return list of avialable encodings621 """622 res = []623 SQL = render_template(624 "/".join([self.template_path, 'get_encodings.sql'])625 )626 status, rset = self.conn.execute_dict(SQL)627 if not status:628 return internal_server_error(errormsg=rset)629 630 for row in rset['rows']:631 res.append(632 {'label': row['encoding'], 'value': row['encoding']}633 )634 635 return make_json_response(636 data=res,637 status=200638 )639 640 @check_precondition(action="get_ctypes")641 def get_ctypes(self, gid, sid, did=None):642 """643 This function to return list of available collation/character types644 """645 res = []646 default_list = ['C', 'POSIX']647 for val in default_list:648 res.append(649 {'label': val, 'value': val}650 )651 SQL = render_template(652 "/".join([self.template_path, 'get_ctypes.sql'])653 )654 status, rset = self.conn.execute_dict(SQL)655 if not status:656 return internal_server_error(errormsg=rset)657 658 for row in rset['rows']:659 if row['cname'] not in default_list:660 res.append({'label': row['cname'], 'value': row['cname']})661 662 return make_json_response(663 data=res,664 status=200665 )666 667 @check_precondition(action="get_icu_locale")668 def get_icu_locale(self, gid, sid, did=None):669 """670 This function is used to get the list of icu locale671 """672 res = []673 SQL = render_template(674 "/".join([self.template_path, 'get_icu_locale.sql'])675 )676 status, rset = self.conn.execute_dict(SQL)677 if not status:678 return internal_server_error(errormsg=rset)679 680 for row in rset['rows']:681 res.append(682 {'label': row['colliculocale'], 'value': row['colliculocale']})683 684 return make_json_response(685 data=res,686 status=200687 )688 689 @check_precondition(action="create")690 def create(self, gid, sid):691 """Create the database."""692 required_args = [693 'name'694 ]695 696 data = request.form if request.form else json.loads(697 request.data698 )699 700 for arg in required_args:701 if arg not in data:702 return make_json_response(703 status=410,704 success=0,705 errormsg=_(706 "Could not find the required parameter ({})."707 ).format(arg)708 )709 # The below SQL will execute CREATE DDL only710 SQL = render_template(711 "/".join([self.template_path, self._CREATE_SQL]),712 data=data, conn=self.conn713 )714 status, msg = self.conn.execute_scalar(SQL)715 if not status:716 return internal_server_error(errormsg=msg)717 718 if 'datacl' in data:719 data['datacl'] = parse_priv_to_db(data['datacl'], 'DATABASE')720 721 # The below SQL will execute rest DMLs because we cannot execute722 # CREATE with any other723 SQL = render_template(724 "/".join([self.template_path, self._GRANT_SQL]),725 data=data, conn=self.conn726 )727 SQL = SQL.strip('\n').strip(' ')728 if SQL and SQL != "":729 status, msg = self.conn.execute_scalar(SQL)730 if not status:731 return internal_server_error(errormsg=msg)732 733 # We need oid of newly created database734 SQL = render_template(735 "/".join([self.template_path, self._PROPERTIES_SQL]),736 name=data['name'], conn=self.conn, last_system_oid=0,737 show_system_objects=self.blueprint.show_system_objects,738 )739 SQL = SQL.strip('\n').strip(' ')740 if SQL and SQL != "":741 status, res = self.conn.execute_dict(SQL)742 if not status:743 return internal_server_error(errormsg=res)744 745 response = res['rows'][0]746 # Add database entry into database table with schema_restrictions.747 database = Database(id=response['did'], server=sid,748 schema_res=','.join(data['schema_res']))749 db.session.add(database)750 db.session.commit()751 752 return jsonify(753 node=self.blueprint.generate_browser_node(754 response['did'],755 sid,756 response['name'],757 icon="icon-database-not-connected",758 connected=False,759 tablespace=response['default_tablespace'],760 allowConn=True,761 canCreate=response['cancreate'],762 canDisconn=True,763 canDrop=True,764 isTemplate=response['is_template']765 )766 )767 768 @staticmethod769 def _update_db_schema_res(data, did, sid):770 database = Database.query.filter_by(id=did, server=sid).first()771 if 'schema_res' in data:772 if database:773 data['schema_res'] = ','.join(data['schema_res'])774 setattr(database, 'schema_res', data['schema_res'])775 else:776 database_obj = Database(id=did, server=sid,777 schema_res=','.join(778 data['schema_res']))779 db.session.add(database_obj)780 781 def _check_rename_db_or_change_table_space(self, data, conn, all_ids):782 783 for action in ["rename_database", "tablespace"]:784 sql = self.get_offline_sql(all_ids['gid'], all_ids['sid'], data,785 all_ids['did'], action)786 sql = sql.strip('\n').strip(' ')787 if sql and sql != "":788 status, msg = conn.execute_scalar(sql)789 if not status:790 # In case of error from server while rename it,791 # reconnect to the database with old name again.792 self.conn = self.manager.connection(793 database=data['old_name'], auto_reconnect=True794 )795 status, errmsg = self.conn.connect()796 if not status:797 current_app.logger.error(798 'Could not reconnected to database(#{0}).\n'799 'Error: {1}'.format(all_ids['did'], errmsg)800 )801 return True, msg802 803 QueryHistory.update_history_dbname(804 current_user.id, all_ids['sid'], data['old_name'],805 data['name'])806 return False, ''807 808 def _fetch_db_details(self, data, did):809 if did is not None:810 # Fetch the name of database for comparison811 status, rset = self.conn.execute_dict(812 render_template(813 "/".join([self.template_path, self._NODES_SQL]),814 did=did, conn=self.conn, last_system_oid=0,815 show_system_objects=self.blueprint.show_system_objects,816 )817 )818 if not status:819 return True, rset820 821 if len(rset['rows']) == 0:822 return gone(823 _('Could not find the database on the server.')824 )825 826 data['old_name'] = (rset['rows'][0])['name']827 if 'name' not in data:828 data['name'] = data['old_name']829 return False, ''830 831 def _reconnect_connect_db(self, data, did):832 if self._db['datallowconn']:833 self.conn = self.manager.connection(834 database=data['name'], auto_reconnect=True835 )836 status, errmsg = self.conn.connect()837 838 if not status:839 current_app.logger.error(840 'Could not connected to database(#{0}).\n'841 'Error: {1}'.format(did, errmsg)842 )843 return True, errmsg844 return False, ''845 846 def _commit_db_changes(self, res, can_drop):847 if self.manager.db == res['name']:848 can_drop = False849 850 try:851 db.session.commit()852 except Exception as e:853 current_app.logger.exception(e)854 return True, e.message, False855 return False, '', can_drop856 857 def _get_data_from_request(self):858 return request.form if request.form else json.loads(859 request.data860 )861 862 @check_precondition(action='update')863 def update(self, gid, sid, did):864 """Update the database."""865 866 data = self._get_data_from_request()867 # Update schema restriction in db object.868 DatabaseView._update_db_schema_res(data, did, sid)869 870 # Generic connection for offline updates871 conn = self.manager.connection(conn_id='db_offline_update')872 status, errmsg = conn.connect()873 if not status:874 current_app.logger.error(875 "Could not create database connection for offline updates\n"876 "Err: {0}".format(errmsg)877 )878 return internal_server_error(errmsg)879 880 fetching_error, err_msg = self._fetch_db_details(data, did)881 if fetching_error:882 return internal_server_error(errormsg=err_msg)883 884 # Release any existing connection from connection manager885 # to perform offline operation886 self.manager.release(did=did)887 all_ids = {888 'gid': gid,889 'sid': sid,890 'did': did891 }892 is_error, errmsg = self._check_rename_db_or_change_table_space(data,893 conn,894 all_ids)895 if is_error:896 return internal_server_error(errmsg)897 898 # Make connection for database again899 connection_error, errmsg = self._reconnect_connect_db(data, did)900 if connection_error:901 return internal_server_error(errmsg)902 903 sql = self.get_online_sql(gid, sid, data, did)904 sql = sql.strip('\n').strip(' ')905 if sql and sql != "":906 status, msg = self.conn.execute_scalar(sql)907 if not status:908 return internal_server_error(errormsg=msg)909 910 # Release any existing connection from connection manager911 # used for offline updates912 self.manager.release(conn_id="db_offline_update")913 914 # Fetch the new data again after update for proper node915 # generation916 status, rset = self.conn.execute_dict(917 render_template(918 "/".join([self.template_path, self._NODES_SQL]),919 did=did, conn=self.conn, last_system_oid=0,920 show_system_objects=self.blueprint.show_system_objects,921 )922 )923 if not status:924 return internal_server_error(errormsg=rset)925 926 if len(rset['rows']) == 0:927 return gone(928 self.not_found_error_msg()929 )930 931 res = rset['rows'][0]932 933 can_drop = True934 error, errmsg, is_can_drop = self._commit_db_changes(res, can_drop)935 if error:936 return make_json_response(937 success=0,938 errormsg=errmsg939 )940 941 can_drop = can_dis_conn = is_can_drop942 943 icon = self.get_icon(res,944 self.conn.connected()945 if self._db['datallowconn'] else False)946 947 return jsonify(948 node=self.blueprint.generate_browser_node(949 did,950 sid,951 res['name'],952 icon=icon,953 connected=self.conn.connected() if954 self._db['datallowconn'] else False,955 tablespace=res['spcname'],956 allowConn=res['datallowconn'],957 canCreate=res['cancreate'],958 canDisconn=can_dis_conn,959 canDrop=can_drop,960 inode=True if res['datallowconn'] else False,961 isTemplate=res['is_template'],962 )963 )964 965 def _release_conn_before_delete(self, sid, did):966 """967 Check connection and release it before deleting database.968 :param sid: Server Id.969 :param did: Database Id.970 :return: Return error if any.971 """972 if self.conn.connected():973 # Release the connection if it is connected974 from pgadmin.utils.driver import get_driver975 manager = \976 get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)977 manager.connection(did=did, auto_reconnect=True)978 status = manager.release(did=did)979 980 if not status:981 return True, unauthorized(982 _("Database could not be deleted."))983 984 return False, ''985 986 @staticmethod987 def _get_req_data(did):988 """989 Get data from request.990 :param did: Database Id.991 :return: Return Data get from request.992 """993 994 if did is None:995 data = request.form if request.form else json.loads(996 request.data997 )998 else:999 data = {'ids': [did]}1000 1001 return data1002 1003 @check_precondition(action="drop")1004 def delete(self, gid, sid, did=None):1005 """Delete the database."""1006 1007 data = DatabaseView._get_req_data(did)1008 1009 for did in data['ids']:1010 default_conn = self.manager.connection()1011 sql = render_template(1012 "/".join([self.template_path, self._DELETE_SQL]),1013 did=did, conn=self.conn1014 )1015 status, res = default_conn.execute_scalar(sql)1016 if not status:1017 return internal_server_error(errormsg=res)1018 1019 if res is None:1020 return make_json_response(1021 status=410,1022 success=0,1023 errormsg=_(1024 'Error: Object not found.'1025 ),1026 info=_(1027 'The specified database could not be found.\n'1028 )1029 )1030 else:1031 is_error, errmsg = self._release_conn_before_delete(sid, did)1032 if is_error:1033 return errmsg1034 1035 sql = render_template(1036 "/".join([self.template_path, self._DELETE_SQL]),1037 datname=res, conn=self.conn,1038 with_force=self.cmd == 'delete'1039 )1040 1041 status, msg = default_conn.execute_scalar(sql)1042 if not status:1043 # reconnect if database drop failed.1044 conn = self.manager.connection(did=did,1045 auto_reconnect=True)1046 status, errmsg = conn.connect()1047 1048 return internal_server_error(1049 errormsg=underscore_escape(msg))1050 1051 return make_json_response(success=1)1052 1053 @check_precondition(action="msql")1054 def msql(self, gid, sid, did=None):1055 """1056 This function to return modified SQL.1057 """1058 data = {}1059 for k, v in request.args.items():1060 try:1061 # comments should be taken as is because if user enters a1062 # json comment it is parsed by loads which should not happen1063 if k in ('comments',):1064 data[k] = v1065 else:1066 data[k] = json.loads(v)1067 except ValueError:1068 data[k] = v1069 status, res = self.get_sql(gid, sid, data, did)1070 1071 if not status:1072 return res1073 1074 res = re.sub('\n{2,}', '\n\n', res)1075 SQL = res.strip('\n').strip(' ')1076 1077 return make_json_response(1078 data=SQL,1079 status=2001080 )1081 1082 def get_sql(self, gid, sid, data, did=None):1083 SQL = ''1084 if did is not None:1085 # Fetch the name of database for comparison1086 conn = self.manager.connection()1087 status, rset = conn.execute_dict(1088 render_template(1089 "/".join([self.template_path, self._NODES_SQL]),1090 did=did, conn=conn, last_system_oid=0,1091 show_system_objects=self.blueprint.show_system_objects,1092 )1093 )1094 if not status:1095 return False, internal_server_error(errormsg=rset)1096 1097 if len(rset['rows']) == 0:1098 return gone(1099 self.not_found_error_msg()1100 )1101 1102 data['old_name'] = (rset['rows'][0])['name']1103 if 'name' not in data:1104 data['name'] = data['old_name']1105 1106 SQL = ''1107 for action in ["rename_database", "tablespace"]:1108 SQL += self.get_offline_sql(gid, sid, data, did, action)1109 1110 SQL += self.get_online_sql(gid, sid, data, did)1111 else:1112 SQL += self.get_new_sql(gid, sid, data, did)1113 1114 return True, SQL1115 1116 def get_new_sql(self, gid, sid, data, did=None):1117 """1118 Generates sql for creating new database.1119 """1120 required_args = [1121 'name'1122 ]1123 1124 for arg in required_args:1125 if arg not in data:1126 return _(" -- definition incomplete")1127 1128 acls = []1129 1130 try:1131 acls = render_template(1132 "/".join([self.template_path, 'allowed_privs.json'])1133 )1134 acls = json.loads(acls)1135 except Exception as e:1136 current_app.logger.exception(e)1137 1138 # Privileges1139 for aclcol in acls:1140 if aclcol in data:1141 allowedacl = acls[aclcol]1142 data[aclcol] = parse_priv_to_db(1143 data[aclcol], allowedacl['acl']1144 )1145 1146 sql_acl = render_template(1147 "/".join([self.template_path, self._GRANT_SQL]),1148 data=data,1149 conn=self.conn1150 )1151 1152 SQL = render_template(1153 "/".join([self.template_path, self._CREATE_SQL]),1154 data=data, conn=self.conn1155 )1156 SQL += "\n"1157 SQL += sql_acl1158 return SQL1159 1160 def get_online_sql(self, gid, sid, data, did=None):1161 """1162 Generates sql for altering database which don not require1163 database to be disconnected before applying.1164 """1165 acls = []1166 try:1167 acls = render_template(1168 "/".join([self.template_path, 'allowed_privs.json'])1169 )1170 acls = json.loads(acls)1171 except Exception as e:1172 current_app.logger.exception(e)1173 1174 # Privileges1175 for aclcol in acls:1176 if aclcol in data:1177 allowedacl = acls[aclcol]1178 1179 for key in ['added', 'changed', 'deleted']:1180 if key in data[aclcol]:1181 data[aclcol][key] = parse_priv_to_db(1182 data[aclcol][key], allowedacl['acl']1183 )1184 1185 return render_template(1186 "/".join([self.template_path, 'alter_online.sql']),1187 data=data, conn=self.conn1188 )1189 1190 def get_offline_sql(self, gid, sid, data, did=None, action=None):1191 """1192 Generates sql for altering database which require1193 database to be disconnected before applying.1194 """1195 1196 return render_template(1197 "/".join([self.template_path, 'alter_offline.sql']),1198 data=data, conn=self.conn, action=action1199 )1200 