Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
utils.py735 linesDownload Raw Back to browser
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"""Browser helper utilities"""11 12from abc import abstractmethod13 14import flask15from flask import render_template, current_app16from flask.views import View, MethodView17from flask_babel import gettext18 19from config import PG_DEFAULT_DRIVER20from pgadmin.utils.ajax import make_json_response, precondition_required,\21    internal_server_error22from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\23    CryptKeyMissing24from pgadmin.utils.constants import DATABASE_LAST_SYSTEM_OID25 26 27def underscore_escape(text):28    """29    This function mimics the behaviour of underscore js escape function30    The html escaped by jinja is not compatible for underscore unescape31    function32    :param text: input html text33    :return: escaped text34    """35    html_map = {36        '&': "&amp;",37        '<': "&lt;",38        '>': "&gt;",39        '"': "&quot;",40        "'": "&#39;"41    }42 43    # always replace & first44    if text:45        for c, r in sorted(html_map.items(),46                           key=lambda x: 0 if x[0] == '&' else 1):47            text = text.replace(c, r)48 49    return text50 51 52def underscore_unescape(text):53    """54    This function mimics the behaviour of underscore js unescape function55    The html unescape by jinja is not compatible for underscore escape56    function57    :param text: input html text58    :return: unescaped text59    """60    html_map = {61        "&amp;": '&',62        "&lt;": '<',63        "&gt;": '>',64        "&quot;": '"',65        "&#39;": "'"66    }67 68    # always replace & first69    if text:70        for c, r in html_map.items():71            text = text.replace(c, r)72 73    return text74 75 76def is_version_in_range(sversion, min_ver, max_ver):77    assert (max_ver is None or isinstance(max_ver, int))78    assert (min_ver is None or isinstance(min_ver, int))79 80    if min_ver is None and max_ver is None:81        return True82 83    if (min_ver is None or min_ver <= sversion) and \84            (max_ver is None or max_ver >= sversion):85        return True86    return False87 88 89class PGChildModule():90    """91    class PGChildModule92 93    This is a base class for children/grand-children of PostgreSQL, and94    all EDB Postgres Advanced Server version95    (i.e. EDB Postgres Advanced Server, Green Plum, etc).96 97    Method:98    ------99    * backend_supported(manager)100    - Return True when it supports certain version.101      Uses the psycopg server connection manager as input for checking the102      compatibility of the current module.103    """104 105    def __init__(self, *args, **kwargs):106        self.min_ver = 0107        self.max_ver = 1100000000108        self.min_ppasver = 0109        self.max_ppasver = 1100000000110        self.server_type = None111 112        super().__init__()113 114    def backend_supported(self, manager, **kwargs):115        if hasattr(self, 'show_node') and not self.show_node:116            return False117 118        sversion = getattr(manager, 'sversion', None)119 120        if sversion is None or not isinstance(sversion, int):121            return False122 123        assert (self.server_type is None or isinstance(self.server_type, list))124 125        if self.server_type is None or manager.server_type in self.server_type:126            min_server_version = self.min_ver127            max_server_version = self.max_ver128            if manager.server_type == 'ppas':129                min_server_version = self.min_ppasver130                max_server_version = self.max_ppasver131            return is_version_in_range(sversion, min_server_version,132                                       max_server_version)133 134        return False135 136    @abstractmethod137    def get_nodes(self, sid=None, **kwargs):138        pass139 140 141class NodeView(View, metaclass=type(MethodView)):142    """143    A PostgreSQL Object has so many operaions/functions apart from CRUD144    (Create, Read, Update, Delete):145    i.e.146    - Reversed Engineered SQL147    - Modified Query for parameter while editing object attributes148      i.e. ALTER TABLE ...149    - Statistics of the objects150    - List of dependents151    - List of dependencies152    - Listing of the children object types for the certain node153      It will used by the browser tree to get the children nodes154 155    This class can be inherited to achieve the diffrent routes for each of the156    object types/collections.157 158       OPERATION   |             URL             | HTTP Method |    Method159    ---------------+-----------------------------+-------------+--------------160    List           | /obj/[Parent URL]/          | GET         | list161    Properties     | /obj/[Parent URL]/id        | GET         | properties162    Create         | /obj/[Parent URL]/          | POST        | create163    Delete         | /obj/[Parent URL]/id        | DELETE      | delete164    Update         | /obj/[Parent URL]/id        | PUT         | update165 166    SQL (Reversed  | /sql/[Parent URL]/id        | GET         | sql167    Engineering)   |168    SQL (Modified  | /msql/[Parent URL]/id       | GET         | modified_sql169    Properties)    |170 171    Statistics     | /stats/[Parent URL]/id      | GET         | statistics172    Dependencies   | /dependency/[Parent URL]/id | GET         | dependencies173    Dependents     | /dependent/[Parent URL]/id  | GET         | dependents174 175    Nodes          | /nodes/[Parent URL]/        | GET         | nodes176    Current Node   | /nodes/[Parent URL]/id      | GET         | node177 178    Children       | /children/[Parent URL]/id   | GET         | children179 180    NOTE:181    Parent URL can be seen as the path to identify the particular node.182 183    i.e.184    In order to identify the TABLE object, we need server -> database -> schema185    information.186    """187    operations = dict({188        'obj': [189            {'get': 'properties', 'delete': 'delete', 'put': 'update'},190            {'get': 'list', 'post': 'create'}191        ],192        'nodes': [{'get': 'node'}, {'get': 'nodes'}],193        'sql': [{'get': 'sql'}],194        'msql': [{'get': 'modified_sql'}],195        'stats': [{'get': 'statistics'}],196        'dependency': [{'get': 'dependencies'}],197        'dependent': [{'get': 'dependents'}],198        'children': [{'get': 'children'}]199    })200 201    @classmethod202    def generate_ops(cls):203        cmds = []204        for op in cls.operations:205            idx = 0206            for ops in cls.operations[op]:207                meths = []208                for meth in ops:209                    meths.append(meth.upper())210                if len(meths) > 0:211                    cmds.append({212                        'cmd': op, 'req': (idx == 0),213                        'with_id': (idx != 2), 'methods': meths214                    })215                idx += 1216        return cmds217 218    # Inherited class needs to modify these parameters219    node_type = None220    # Inherited class needs to modify these parameters221    node_label = None222    # This must be an array object with attributes (type and id)223    parent_ids = []224    # This must be an array object with attributes (type and id)225    ids = []226 227    @classmethod228    def get_node_urls(cls):229        assert cls.node_type is not None, \230            "Please set the node_type for this class ({0})".format(231                str(cls.__class__.__name__))232        common_url = '/'233        for p in cls.parent_ids:234            common_url += '<{0}:{1}>/'.format(str(p['type']), str(p['id']))235 236        id_url = None237        for p in cls.ids:238            id_url = '{0}<{1}:{2}>'.format(239                common_url if not id_url else id_url,240                p['type'], p['id'])241 242        return id_url, common_url243 244    def __init__(self, **kwargs):245        self.cmd = kwargs['cmd']246 247    # Check the existance of all the required arguments from parent_ids248    # and return combination of has parent arguments, and has id arguments249    def check_args(self, **kwargs):250        has_id = has_args = True251        for p in self.parent_ids:252            if p['id'] not in kwargs:253                has_args = False254                break255 256        for p in self.ids:257            if p['id'] not in kwargs:258                has_id = False259                break260 261        return has_args, has_id and has_args262 263    def dispatch_request(self, *args, **kwargs):264        http_method = flask.request.method.lower()265        if http_method == 'head':266            http_method = 'get'267 268        assert self.cmd in self.operations, \269            'Unimplemented command ({0}) for {1}'.format(270                self.cmd,271                str(self.__class__.__name__)272            )273 274        has_args, has_id = self.check_args(**kwargs)275 276        assert (277            self.cmd in self.operations and278            (has_id and len(self.operations[self.cmd]) > 0 and279             http_method in self.operations[self.cmd][0]) or280            (not has_id and len(self.operations[self.cmd]) > 1 and281             http_method in self.operations[self.cmd][1]) or282            (len(self.operations[self.cmd]) > 2 and283             http_method in self.operations[self.cmd][2])284        ), \285            'Unimplemented method ({0}) for command ({1}), which {2} ' \286            'an id'.format(http_method,287                           self.cmd,288                           'requires' if has_id else 'does not require')289        meth = None290        if has_id:291            meth = self.operations[self.cmd][0][http_method]292        elif has_args and http_method in self.operations[self.cmd][1]:293            meth = self.operations[self.cmd][1][http_method]294        else:295            meth = self.operations[self.cmd][2][http_method]296 297        method = getattr(self, meth, None)298 299        if method is None:300            return make_json_response(301                status=406,302                success=0,303                errormsg=gettext(304                    'Unimplemented method ({0}) for this url ({1})').format(305                        meth, flask.request.path306                )307            )308 309        return method(*args, **kwargs)310 311    @classmethod312    def register_node_view(cls, blueprint):313        cls.blueprint = blueprint314        id_url, url = cls.get_node_urls()315 316        commands = cls.generate_ops()317 318        for c in commands:319            cmd = c['cmd'].replace('.', '-')320            if c['with_id']:321                blueprint.add_url_rule(322                    '/{0}{1}'.format(323                        c['cmd'], id_url if c['req'] else url324                    ),325                    view_func=cls.as_view(326                        '{0}{1}'.format(327                            cmd, '_id' if c['req'] else ''328                        ),329                        cmd=c['cmd']330                    ),331                    methods=c['methods']332                )333            else:334                blueprint.add_url_rule(335                    '/{0}'.format(c['cmd']),336                    view_func=cls.as_view(337                        cmd, cmd=c['cmd']338                    ),339                    methods=c['methods']340                )341 342    def children(self, *args, **kwargs):343        """Build a list of treeview nodes from the child nodes."""344        children = self.get_children_nodes(*args, **kwargs)345 346        # Return sorted nodes based on label347        return make_json_response(348            data=sorted(349                children, key=lambda c: c['label']350            )351        )352 353    def get_children_nodes(self, *args, **kwargs):354        """355        Returns the list of children nodes for the current nodes. Override this356        function for special cases only.357 358        :param args:359        :param kwargs: Parameters to generate the correct set of tree node.360        :return: List of the children nodes361        """362        children = []363 364        for module in self.blueprint.submodules:365            children.extend(module.get_nodes(*args, **kwargs))366 367        return children368 369 370class PGChildNodeView(NodeView):371 372    _NODE_SQL = 'node.sql'373    _NODES_SQL = 'nodes.sql'374    _COUNT_SQL = 'count.sql'375    _CREATE_SQL = 'create.sql'376    _UPDATE_SQL = 'update.sql'377    _ALTER_SQL = 'alter.sql'378    _PROPERTIES_SQL = 'properties.sql'379    _DELETE_SQL = 'delete.sql'380    _GRANT_SQL = 'grant.sql'381    _SCHEMA_SQL = 'schema.sql'382    _ACL_SQL = 'acl.sql'383    _OID_SQL = 'get_oid.sql'384    _FUNCTIONS_SQL = 'functions.sql'385    _GET_CONSTRAINTS_SQL = 'get_constraints.sql'386    _GET_TABLES_SQL = 'get_tables.sql'387    _GET_DEFINITION_SQL = 'get_definition.sql'388    _GET_SCHEMA_OID_SQL = 'get_schema_oid.sql'389    _GET_COLUMNS_SQL = 'get_columns.sql'390    _GET_COLUMNS_FOR_TABLE_SQL = 'get_columns_for_table.sql'391    _GET_SUBTYPES_SQL = 'get_subtypes.sql'392    _GET_EXTERNAL_FUNCTIONS_SQL = 'get_external_functions.sql'393    _GET_TABLE_FOR_PUBLICATION = 'get_tables.sql'394    _DATABASE_LAST_SYSTEM_OID = DATABASE_LAST_SYSTEM_OID395 396    def get_children_nodes(self, manager, **kwargs):397        """398        Returns the list of children nodes for the current nodes.399 400        :param manager: Server Manager object401        :param kwargs: Parameters to generate the correct set of browser tree402          node403        :return:404        """405        nodes = []406        for module in self.blueprint.submodules:407            if isinstance(module, PGChildModule):408                if (409                    manager is not None and410                    module.backend_supported(manager, **kwargs)411                ):412                    nodes.extend(module.get_nodes(**kwargs))413            else:414                nodes.extend(module.get_nodes(**kwargs))415        return nodes416 417    def children(self, **kwargs):418        """Build a list of treeview nodes from the child nodes."""419 420        if 'sid' not in kwargs:421            return precondition_required(422                gettext('Required properties are missing.')423            )424 425        from pgadmin.utils.driver import get_driver426        manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(427            sid=kwargs['sid']428        )429 430        did = None431        if 'did' in kwargs:432            did = kwargs['did']433 434        try:435            conn = manager.connection(did=did)436            if not conn.connected():437                status, msg = conn.connect()438                if not status:439                    return internal_server_error(errormsg=msg)440        except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):441            raise442        except Exception:443            return precondition_required(444                gettext(445                    "Connection to the server has been lost."446                )447            )448 449        # Return sorted nodes based on label450        return make_json_response(451            data=sorted(452                self.get_children_nodes(manager, **kwargs),453                key=lambda c: c['label']454            )455        )456 457    def get_dependencies(self, conn, object_id, where=None,458                         show_system_objects=None, is_schema_diff=False):459        """460        This function is used to fetch the dependencies for the selected node.461 462        Args:463            conn: Connection object464            object_id: Object Id of the selected node.465            where: where clause for the sql query (optional)466            show_system_objects: System object status467            is_schema_diff: True when function gets called from schema diff.468 469        Returns: Dictionary of dependencies for the selected node.470        """471 472        # Set the sql_path473        sql_path = 'depends/{0}/#{1}#'.format(474            conn.manager.server_type, conn.manager.version)475 476        if where is None:477            where_clause = "WHERE dep.objid={0}::oid".format(object_id)478        else:479            where_clause = where480 481        query = render_template("/".join([sql_path, 'dependencies.sql']),482                                where_clause=where_clause,483                                object_id=object_id)484        # fetch the dependency for the selected object485        dependencies = self.__fetch_dependency(486            conn, query, show_system_objects, is_schema_diff)487 488        # fetch role dependencies489        if where_clause.find('subid') < 0:490            sql = render_template(491                "/".join([sql_path, 'role_dependencies.sql']),492                where_clause=where_clause, conn=conn)493 494            status, result = conn.execute_dict(sql)495            if not status:496                current_app.logger.error(result)497 498            for row in result['rows']:499                ref_name = row['refname']500                dep_str = row['deptype']501                dep_type = ''502 503                if dep_str == 'a':504                    dep_type = 'ACL'505                elif dep_str == 'o':506                    dep_type = 'Owner'507 508                if row['refclassid'] == 1260:509                    dependencies.append(510                        {'type': 'role',511                         'name': ref_name,512                         'field': dep_type}513                    )514 515        return dependencies516 517    def get_dependents(self, conn, object_id, where=None):518        """519        This function is used to fetch the dependents for the selected node.520 521        Args:522            conn: Connection object523            object_id: Object Id of the selected node.524            where: where clause for the sql query (optional)525 526        Returns: Dictionary of dependents for the selected node.527        """528        # Set the sql_path529        sql_path = 'depends/{0}/#{1}#'.format(530            conn.manager.server_type, conn.manager.version)531 532        if where is None:533            where_clause = "WHERE dep.refobjid={0}::oid".format(object_id)534        else:535            where_clause = where536 537        query = render_template("/".join([sql_path, 'dependents.sql']),538                                where_clause=where_clause)539        # fetch the dependency for the selected object540        dependents = self.__fetch_dependency(conn, query)541 542        return dependents543 544    def __fetch_dependency(self, conn, query, show_system_objects=None,545                           is_schema_diff=False):546        """547        This function is used to fetch the dependency for the selected node.548 549        Args:550            conn: Connection object551            query: sql query to fetch dependencies/dependents552            show_system_objects: System object status553            is_schema_diff: True when function gets called from schema diff.554 555        Returns: Dictionary of dependency for the selected node.556        """557 558        standard_types = {559            'r': None,560            'i': 'index',561            'S': 'sequence',562            'v': 'view',563            'p': 'partition_table',564            'f': 'foreign_table',565            'm': 'materialized_view',566            't': 'toast_table',567            'I': 'partition_index'568        }569 570        # Dictionary for the object types571        custom_types = {572            'x': 'external_table', 'n': 'schema', 'd': 'domain',573            'l': 'language', 'Cc': 'check', 'Cd': 'domain_constraints',574            'Cf': 'foreign_key', 'Cp': 'primary_key', 'Co': 'collation',575            'Cu': 'unique_constraint', 'Cx': 'exclusion_constraint',576            'Fw': 'foreign_data_wrapper', 'Fs': 'foreign_server',577            'Fc': 'fts_configuration', 'Fp': 'fts_parser',578            'Fd': 'fts_dictionary', 'Ft': 'fts_template',579            'Ex': 'extension', 'Et': 'event_trigger', 'Pa': 'package',580            'Pf': 'function', 'Pt': 'trigger_function', 'Pp': 'procedure',581            'Rl': 'rule', 'Rs': 'row_security_policy', 'Sy': 'synonym',582            'Ty': 'type', 'Tr': 'trigger', 'Tc': 'compound_trigger',583            'c': 'type',584            # None specified special handling for this type585            'A': None586        }587 588        # Merging above two dictionaries589        types = {**standard_types, **custom_types}590 591        # Dictionary for the restrictions592        dep_types = {593            # None specified special handling for this type594            'n': 'normal',595            'a': 'auto',596            'i': None,597            'p': None598        }599 600        status, result = conn.execute_dict(query)601        if not status:602            current_app.logger.error(result)603 604        dependency = list()605 606        for row in result['rows']:607            _ref_name = row['refname']608            type_str = row['type']609            dep_str = row['deptype']610            nsp_name = row['nspname']611            object_id = None612            if 'refobjid' in row:613                object_id = row['refobjid']614 615            ref_name = ''616            if nsp_name is not None:617                ref_name = nsp_name + '.'618 619            type_name = ''620            icon = None621 622            # Fetch the type name from the dictionary623            # if type is not present in the types dictionary then624            # we will continue and not going to add it.625            if len(type_str) and type_str in types and \626                    types[type_str] is not None:627                type_name = types[type_str]628                if type_str == 'Rl':629                    ref_name = \630                        _ref_name + ' ON ' + ref_name + row['ownertable']631                    _ref_name = None632                elif type_str == 'Cf':633                    ref_name += row['ownertable'] + '.'634                elif type_str == 'm':635                    icon = 'icon-mview'636            elif len(type_str) and type_str[0] in types and \637                    types[type_str[0]] is None:638                # if type is present in the types dictionary, but it's639                # value is None then it requires special handling.640                if type_str[0] == 'r':641                    if (len(type_str) > 1 and type_str[1].isdigit() and642                        int(type_str[1]) > 0) or \643                        (len(type_str) > 2 and type_str[2].isdigit() and644                         int(type_str[2]) > 0):645                        type_name = 'column'646                    else:647                        type_name = 'table'648                        if 'is_inherits' in row and row['is_inherits'] == '1':649                            if 'is_inherited' in row and \650                                    row['is_inherited'] == '1':651                                icon = 'icon-table-multi-inherit'652                            # For tables under partitioned tables,653                            # is_inherits will be true and dependency654                            # will be auto as it inherits from parent655                            # partitioned table656                            elif ('is_inherited' in row and657                                  row['is_inherited'] == '0') and \658                                    dep_str == 'a':659                                type_name = 'partition'660                            else:661                                icon = 'icon-table-inherits'662                        elif 'is_inherited' in row and \663                                row['is_inherited'] == '1':664                            icon = 'icon-table-inherited'665                elif type_str[0] == 'A':666                    # Include only functions667                    if row['adbin'].startswith('{FUNCEXPR'):668                        type_name = 'function'669                        ref_name = row['adsrc']670                    else:671                        continue672            else:673                continue674 675            if _ref_name is not None:676                ref_name += _ref_name677 678            # If schema diff is set to True then we don't need to calculate679            # field and also no need to add icon and field in the list.680            if is_schema_diff and type_name != 'schema':681                dependency.append(682                    {683                        'type': type_name,684                        'name': ref_name,685                        'oid': object_id686                    }687                )688            elif not is_schema_diff:689                dep_type = ''690                if show_system_objects is None:691                    show_system_objects = self.blueprint.show_system_objects692                if dep_str[0] in dep_types:693                    # if dep_type is present in the dep_types dictionary,694                    # but it's value is None then it requires special695                    # handling.696                    if dep_types[dep_str[0]] is None:697                        if dep_str[0] == 'i':698                            if show_system_objects:699                                dep_type = 'internal'700                            else:701                                continue702                        elif dep_str[0] == 'p':703                            dep_type = 'pin'704                            type_name = ''705                    else:706                        dep_type = dep_types[dep_str[0]]707 708                dependency.append(709                    {710                        'type': type_name,711                        'name': ref_name,712                        'field': dep_type,713                        'icon': icon,714                    }715                )716 717        return dependency718 719    def _check_cascade_operation(self, only_sql=None):720        """721        Check cascade operation.722        :param only_sql:723        :return:724        """725        if self.cmd == 'delete' or only_sql:726            # This is a cascade operation727            cascade = True728        else:729            cascade = False730        return cascade731 732    def not_found_error_msg(self, custom_label=None):733        return gettext("Could not find the specified {}.".format(734            custom_label if custom_label else self.node_label).lower())735 
codekingpro/portable-devtools · Team Ai