Subject: PATCH: pgadmin 4: FTS Parser

From: Sanket Mehta <[email-masked]>
Date: Mon, 28 Mar 2016 14:32:46 +0530

Hi,

PFA the patch for FTS parser node for review.
Please do review it and provide the comments.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py
new file mode 100644
index 0000000..90a64b5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py
@@ -0,0 +1,816 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines views for management of Fts Parser node"""
+
+import json
+from flask import render_template, make_response, current_app, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.browser.server_groups.servers.databases.schemas.utils import SchemaChildModule
+import pgadmin.browser.server_groups.servers.databases.schemas as schemas
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class FtsParserModule(SchemaChildModule):
+    """
+     class FtsParserModule(SchemaChildModule)
+
+        A module class for FTS Parser node derived from SchemaChildModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the FtsParserModule and it's base module.
+
+    * get_nodes(gid, sid, did, scid)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for FTS Parser, when any of the schema node is
+        initialized.
+    """
+    NODE_TYPE = 'fts_parser'
+    COLLECTION_LABEL = gettext('FTS Parsers')
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+        super(FtsParserModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, scid):
+        """
+        Generate the collection node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        yield self.generate_browser_collection_node(scid)
+
+    @property
+    def node_inode(self):
+        """
+        Override the property to make the node as leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for fts template, when any of the schema node is
+        initialized.
+        """
+        return schemas.SchemaModule.NODE_TYPE
+
+
+blueprint = FtsParserModule(__name__)
+
+
+class FtsParserView(PGChildNodeView):
+    """
+    class FtsParserView(PGChildNodeView)
+
+        A view class for FTS Parser node derived from PGChildNodeView. This class is
+        responsible for all the stuff related to view like create/update/delete
+        responsible for all the stuff related to view like create/update/delete
+        FTS Parser, showing properties of node, showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the FtsParserView and it's base view.
+
+    * module_js()
+      - This property defines (if javascript) exists for this node.
+        Override this property for your own logic
+
+    * check_precondition()
+      - 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
+
+    * list()
+      - This function is used to list all the  nodes within that collection.
+
+    * nodes()
+      - This function will used to create all the child node within that collection.
+        Here it will create all the FTS Parser nodes.
+
+    * properties(gid, sid, did, scid, pid)
+      - This function will show the properties of the selected FTS Parser node
+
+    * create(gid, sid, did, scid)
+      - This function will create the new FTS Parser object
+
+    * update(gid, sid, did, scid, pid)
+      - This function will update the data for the selected FTS Parser node
+
+    * delete(self, gid, sid, did, scid, pid):
+      - This function will drop the FTS Parser object
+
+    * msql(gid, sid, did, scid, pid)
+      - This function is used to return modified SQL for the selected FTS Parser node
+
+    * get_sql(data, pid)
+      - This function will generate sql from model data
+
+    * sql(gid, sid, did, scid, pid):
+      - This function will generate sql to show it in sql pane for the selected FTS Parser node.
+
+    * get_type():
+      - This function will fetch all the types for source and target types select control.
+
+    * dependents(gid, sid, did, scid, pid):
+      - This function get the dependents and return ajax response for the Fts Parser node.
+
+    * dependencies(self, gid, sid, did, scid, pid):
+      - This function get the dependencies and return ajax response for the FTS Parser node.
+
+    """
+
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'},
+        {'type': 'int', 'id': 'scid'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'pid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'children': [{
+            'get': 'children'
+        }],
+        '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'}],
+        'get_start': [{'get': 'get_start'}, {'get': 'get_start'}],
+        'get_token': [{'get': 'get_token'}, {'get': 'get_token'}],
+        'get_end': [{'get': 'get_end'}, {'get': 'get_end'}],
+        'get_lextype': [{'get': 'get_lextype'}, {'get': 'get_lextype'}],
+        'get_headline': [{'get': 'get_headline'}, {'get': 'get_headline'}],
+    })
+
+    def _init_(self, **kwargs):
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+        super(FtsParserView, self).__init__(**kwargs)
+
+    def module_js(self):
+        """
+        This property defines whether javascript exists for this node.
+        """
+        return make_response(
+            render_template(
+                "fts_parser/js/fts_parser.js",
+                _=gettext
+            ),
+            200, {'Content-Type': 'application/x-javascript'}
+        )
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                        "Connection to the server has been lost!"
+                    )
+                )
+            # we will set template path for sql scripts depending upon server version
+            ver = self.manager.version
+            if ver >= 90100:
+                self.template_path = 'fts_parser/sql/9.1_plus'
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid
+        )
+        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, scid):
+        res = []
+        sql = render_template(
+            "/".join([self.template_path, 'nodes.sql']),
+            scid=scid
+        )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-fts_parser"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, scid, pid):
+        res = []
+        sql = render_template(
+            "/".join([self.template_path, 'nodes.sql']),
+            pid=pid
+        )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                        row['oid'],
+                        did,
+                        row['name'],
+                        icon="icon-fts_parser"
+                    ),
+                status=200
+            )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, pid):
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid,
+            pid=pid
+        )
+        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, scid):
+        """
+        This function will creates new the fts_parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+
+        # Mandatory fields to create a new fts parser
+        required_args = [
+            'prsstart',
+            'prstoken',
+            'prsend',
+            'prslextype',
+            'schema',
+            '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
+                    )
+                )
+        try:
+            # Fetch schema name from schema oid
+            sql = render_template("/".join([self.template_path, 'schema.sql']),
+                                  data=data,
+                                  conn=self.conn,
+                                  )
+
+            status, schema = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=schema)
+
+            # replace schema oid with schema name before passing to create.sql
+            # to generate proper sql query
+            new_data = data.copy()
+            new_data['schema'] = schema
+            sql = render_template("/".join([self.template_path, 'create.sql']),
+                                  data=new_data,
+                                  conn=self.conn,
+                                  )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # we need fts_parser id to to add object in tree at browser,
+            # below sql will give the same
+            sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                name=data['name']
+            )
+            status, pid = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=pid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    pid,
+                    did,
+                    data['name'],
+                    icon="icon-fts_parser"
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, scid, pid):
+        """
+        This function will update text search parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+
+        # Fetch sql query to update fts parser
+        sql = self.get_sql(gid, sid, did, scid, data, pid)
+        try:
+            if sql and sql.strip('\n') and sql.strip(' '):
+                status, res = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="FTS Parser updated",
+                    data={
+                        'id': pid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id': pid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, scid, pid):
+        """
+        This function will drop the fts_parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name for Parser from pid
+            sql = render_template("/".join([self.template_path, 'delete.sql']),
+                                  pid=pid)
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # Drop fts Parser
+            result = res['rows'][0]
+            sql = render_template("/".join([self.template_path, 'delete.sql']),
+                                  name=result['name'],
+                                  schema=result['schema'],
+                                  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("FTS Parser dropped"),
+                data={
+                    'id': pid,
+                    'sid': sid,
+                    'gid': gid,
+                    'did': did,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, scid, pid=None):
+        """
+        This function returns modified SQL
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        data = request.args
+
+        # Fetch sql query for modified data
+        sql = self.get_sql(gid, sid, did, scid, data, pid)
+
+        if isinstance(sql, str) and sql and sql.strip('\n') and sql.strip(' '):
+            return make_json_response(
+                data=sql,
+                status=200
+            )
+        else:
+            return make_json_response(
+                data="--modified SQL",
+                status=200
+            )
+
+    def get_sql(self, gid, sid, did, scid, data, pid=None):
+        """
+        This function will return SQL for model data
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        try:
+            # Fetch sql for update
+            if pid is not None:
+                sql = render_template(
+                    "/".join([self.template_path, 'properties.sql']),
+                    pid=pid,
+                    scid=scid
+                )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                old_data = res['rows'][0]
+
+                # If user has changed the schema then fetch new schema directly
+                # using its oid otherwise fetch old schema name using fts Parser oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data)
+
+                status, new_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=new_schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                if 'schema' in new_data:
+                    new_data['schema'] = new_schema
+
+                # Fetch old schema name using old schema oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=old_data)
+
+                status, old_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=old_schema)
+
+                # Replace old schema oid with old schema name
+                old_data['schema'] = old_schema
+
+                sql = render_template(
+                    "/".join([self.template_path, 'update.sql']),
+                    data=new_data, o_data=old_data
+                )
+                # Fetch sql query for modified data
+            else:
+                # Fetch schema name from schema oid
+                sql = render_template("/".join([self.template_path, 'schema.sql']),
+                                      data=data)
+
+                status, schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                new_data['schema'] = schema
+
+                if 'prsstart' in new_data and \
+                        'prstoken' in new_data and \
+                        'prsend' in new_data and \
+                        'prslextype' in new_data and \
+                        'name' in new_data and \
+                        'schema' in new_data:
+                    sql = render_template("/".join([self.template_path,
+                                                    'create.sql']),
+                                          data=new_data,
+                                          conn=self.conn
+                                          )
+                else:
+                    sql = "-- incomplete definition"
+            return str(sql.strip('\n'))
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def get_start(self, gid, sid, did, scid, pid=None):
+        """
+        This function will return start functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              start=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at start select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def get_token(self, gid, sid, did, scid, pid=None):
+        """
+        This function will return token functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              token=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at token select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def get_end(self, gid, sid, did, scid, pid=None):
+        """
+        This function will return end functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              end=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at end select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def get_lextype(self, gid, sid, did, scid, pid=None):
+        """
+        This function will return lextype functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              lextype=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at lextype select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def get_headline(self, gid, sid, did, scid, pid=None):
+        """
+        This function will return headline functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              headline=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at headline select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, pid):
+        """
+        This function will reverse generate sql for sql panel
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        try:
+            sql = render_template(
+                "/".join([self.template_path, 'sql.sql']),
+                pid=pid,
+                scid=scid,
+                conn=self.conn
+            )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered Query for the FTS Parser!\n{0}").format(
+                        res
+                    )
+                )
+
+            if res is None:
+                return gone(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered Query for FTS Parser node!")
+                )
+
+            return ajax_response(response=res)
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def dependents(self, gid, sid, did, scid, pid):
+        """
+        This function get the dependents and return ajax response
+        for the FTS Parser node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            pid: FTS Parser ID
+        """
+        dependents_result = self.get_dependents(self.conn, pid)
+        return ajax_response(
+                response=dependents_result,
+                status=200
+                )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, scid, pid):
+        """
+        This function get the dependencies and return ajax response
+        for the FTS Parser node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            pid: FTS Tempalte ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, pid)
+        return ajax_response(
+                response=dependencies_result,
+                status=200
+                )
+
+FtsParserView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/coll-fts_parser.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/coll-fts_parser.png
new file mode 100644
index 0000000000000000000000000000000000000000..340ec6b7fbdcf1702f66fe2c833fcdcd512bb141
GIT binary patch
literal 620
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47+C{+LR^8|m3!MN7a1f>VVS)(
zuW6md$rp<r{6BdA|NguGcHjQD?dIPN*Z-`z`g_TNr=9g{4YFrR-1xeE_4R_vRqAES
z71C!2EI5^vGfy;Uw$zRP8?XFZe&OHzvwx<a`rUWzSIeOvmHWQu@BdbC?0v`FZ3XxL
z@4NVK(QcLNhkw@0O`Wt$`Rbm@lMjC_Nt?lc``?z+e<q*!)p_J+-GT4LhdWo_On!X#
z$ksbYHrz<Md$@hY^*<|*{%mSo=3lqeW5?I{0}_v}99lJFSJ1Sbz7MW0KdJou%!iqW
zew6R}n!NRM*oIGDyV|B)Pr7sS@UokS7hO-fb)a!s^G4I7UuzD1E!_V(bI+%w1CwVT
zH@UUHZ(jX+&1wblhrobfED7=pW^j0RBMr#mEbxdd2GSm2>~=ES4#=42>Eak7aXC5R
z07H+@8G!>wIG%0ZuxVp)K~dr751&46ZeU?yX>D2P<>KPVbS5doYsS%wQw$bsPh`-V
zIAzkbiBl(s2ZRVEBm|_Tg$0HN2M1pdxP0YO8r!RjiPx@PHa9RaGBY$aX6Mu}j*f_m
zynW-=O=}CQMiz;ZIk7Q!@7UVx-BVkm>cDiW&g&mzqvJtE#f6Ed1g>*C+*Leq$bp4{
z;lM(nj5%k`L4Hv!ag8WRNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KrrHLkRt5(1
t-s!DE(U6;;l9^Ts(O_T+)&Nv(Vr5_k(Qs;d=o+8~22WQ%mvv4FO#s2@A-Vtn

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/fts_parser.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/fts_parser.png
new file mode 100644
index 0000000000000000000000000000000000000000..47eca9950499fc0ca1e2ee0a419a8a380fd528f3
GIT binary patch
literal 715
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47-Ir_LR^8|m3!MN7a1f>VVS)(
zuW6md$rp<r{6Bd2-<~`Fw%_`<>H42FSAQ?N{CnQTU$gf;X{leUmpx13#@FpP{%^Sa
zZ|V8JbI<;ncItQEnctJH|6YFO*MdtwXJ7s?=gh~+_y6zPt#bY3@9s&PGFKn&x-jF&
z5v?ajv>tCMU3>k<lImr8cmC}-_jl&$KNC;<>OAtZ;p(r&7rIZ}On!3j=#D!_w%ka%
zccJUVt-l-FS0uEqh&ueUwsD#7io?yvWZpcvzGK#|sF}M$A6{E?R^`L>KdX-YYB}_y
zYTx&Q-QUvA|Cn^9Yu}Be`?n9Ty>)oi^`u+pIuBm?y{K_nK>ae$+9gigzC^aI@m_SW
zcvI=}Q>G6OY2I63yx{!TzQP3xIkTlseVBgmd+E-viCaI1ZT#f3_M_9LkA8>0lpOq=
zci>aj;ZOPdUl-2ZR?xiB?D+fc6W<z+d@VotC4c|tj6I){c7BT9{xNL(hmh?r<LlRJ
z{;19`0fsMQNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*lmp7sn8Z%gG4}Oa?}V&Wys!
z9p?|7I(F{h$)jfvpFXaCfJsC^L`Y0fRCxM?DU+s66lLKuIDPW;X>AQH&DASbt;}v<
zG1<DMwB+j-UY_0_-vdm`(w>P;3%h2vt?V0L0;9Wnx_G#GIlsStzWj!U_4Df;5&{Z5
zG9oHmR00&Vtklf(>=X?(Emci*xilusn>cgo+{v@2#|K1&%;77Wsc>d#lg^PJJivq_
zeouzQkE7)|(8a1Ht`Q|Ei6yC4$wjF^iowXh&_LJFNY}tH#K^$P#MsKjRNKJR%D`aW
tJH1sX8glbfGSey{8VoGK8i1-ztPIQ`8ct0QT?5p>;OXk;vd$@?2>`SDU7!E}

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/js/fts_parser.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/js/fts_parser.js
new file mode 100644
index 0000000..0014427
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/js/fts_parser.js
@@ -0,0 +1,169 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+  // Extend the collection class for fts parser
+  if (!pgBrowser.Nodes['coll-fts_parser']) {
+    var fts_parsers = pgAdmin.Browser.Nodes['coll-fts_parser'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'fts_parser',
+        label: '{{ _('FTS Parsers') }}',
+        type: 'coll-fts_parser',
+        columns: ['name', 'description']
+      });
+  };
+
+  // Extend the node class for fts parser
+  if (!pgBrowser.Nodes['fts_parser']) {
+    pgAdmin.Browser.Nodes['fts_parser'] = pgAdmin.Browser.Node.extend({
+      parent_type: ['schema', 'catalog'],
+      type: 'fts_parser',
+      canDrop: true,
+      canDropCascade: true,
+      label: '{{ _('FTS Parsers') }}',
+      hasSQL: true,
+      hasDepends: true,
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Add context menus for fts parser
+        pgBrowser.add_menus([{
+          name: 'create_fts_parser_on_schema', node: 'schema', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Parser...') }}',
+          icon: 'wcTabIcon icon-fts_parser', data: {action: 'create'}
+          },{
+          name: 'create_fts_parser_on_coll', node: 'coll-fts_parser', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Parser...') }}',
+          icon: 'wcTabIcon icon-fts_parser', data: {action: 'create'}
+          },{
+          name: 'create_fts_parser', node: 'fts_parser', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Parser...') }}',
+          icon: 'wcTabIcon icon-fts_parser', data: {action: 'create'}
+          }]);
+
+      },
+
+      // Defining backform model for fts parser node
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,      // Fts parser name
+          description: undefined,   // Comment on parser
+          schema: undefined,        // Schema name to which parser belongs
+          tmplinit: undefined,      // Init function for fts parser
+          tmpllexize: undefined     // Lexize function for fts parser
+        },
+        initialize: function() {
+            pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
+            if (_.isUndefined(this.get('schema'))) {
+                this.set('schema', this.node_info.schema._id);
+            }
+        },
+        // Defining schema for fts parser
+        schema: [{
+          id: 'name', label: '{{ _('Name') }}', cell: 'string',
+          type: 'text', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'oid', label:'{{ _('OID') }}', cell: 'string',
+          editable: false, type: 'text', disabled: true, mode:['properties']
+        },{
+          id: 'schema', label: '{{ _('Schema')}}', cell: 'string',
+          type: 'text', mode: ['create','edit'], node: 'schema',
+          control: 'node-list-by-id'
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string',
+          type: 'multiline', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'prsstart', label: '{{ _('Start Function')}}', group: '{{ _('Definition') }}',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_start'
+        },{
+          id: 'prstoken', label: '{{ _('Gettoken Function')}}', group: '{{ _('Definition') }}',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_token'
+        },{
+          id: 'prsend', label: '{{ _('End Function')}}', group: '{{ _('Definition') }}',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_end'
+        },{
+          id: 'prslextype', label: '{{ _('Lextypes Function')}}', group: '{{ _('Definition') }}',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_lextype'
+        },{
+          id: 'prsheadline', label: '{{ _('Headline Function')}}', group: '{{ _('Definition') }}',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_headline'
+        }],
+
+        /*
+         * Triggers control specific error messages for parser name,
+         * lexize function and schema, if any one of them is not specified
+         * while creating new fts parser
+         */
+        validate: function(keys){
+          var name = this.get('name');
+          var start = this.get('prsstart');
+          var token = this.get('prstoken');
+          var end = this.get('prsend');
+          var lextype = this.get('prslextype');
+          var schema = this.get('schema');
+
+          // Validate fts parser name
+          if (_.isUndefined(name) || _.isNull(name) || String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name must be specified!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          }
+
+          // Validate start function control
+          else if (_.isUndefined(start) || _.isNull(start) || String(start).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Start function must be selected!') }}';
+            this.errorModel.set('prsstart', msg);
+            return msg;
+          }
+
+          // Validate gettoken function control
+          else if (_.isUndefined(token) || _.isNull(token) || String(token).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Gettoken function must be selected!') }}';
+            this.errorModel.set('prstoken', msg);
+            return msg;
+          }
+
+          // Validate end function control
+          else if (_.isUndefined(end) || _.isNull(end) || String(end).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('End function must be selected!') }}';
+            this.errorModel.set('prsend', msg);
+            return msg;
+          }
+
+          // Validate lextype function control
+          else if (_.isUndefined(lextype) || _.isNull(lextype) || String(lextype).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Lextype function must be selected!') }}';
+            this.errorModel.set('prslextype', msg);
+            return msg;
+          }
+
+          // Validate schema for fts parser
+          else if (_.isUndefined(schema) || _.isNull(schema) || String(schema).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Schema must be selected!') }}';
+            this.errorModel.set('schema', msg);
+            return msg;
+          }
+          else this.errorModel.clear();
+
+          this.trigger('on-status-clear');
+          return null;
+        }
+      })
+    });
+  }
+
+return pgBrowser.Nodes['coll-fts_parser'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/create.sql
new file mode 100644
index 0000000..de6e5b1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/create.sql
@@ -0,0 +1,14 @@
+{# CREATE FTS PARSER Statement #}
+{% if data and data.schema and data.name and data.prsstart and data.prstoken and data.prsend and data.prslextype %}
+CREATE TEXT SEARCH PARSER {{ conn|qtIdent(data.schema, data.name) }} (
+    START = {{data.prsstart}},
+    GETTOKEN = {{data.prstoken}},
+    END = {{data.prsend}},
+    LEXTYPES = {{data.prslextype}}{% if data.prsheadline and data.prsheadline != '-'%},
+    HEADLINE = {{data.prsheadline}},{% endif %}
+);
+{# Description for FTS_PARSER #}
+{% if data.description %}
+COMMENT ON TEXT SEARCH PARSER {{ conn|qtIdent(data.schema, data.name) }}
+      IS {{ data.description|qtLiteral }};
+{% endif %}{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/delete.sql
new file mode 100644
index 0000000..cf2b533
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/delete.sql
@@ -0,0 +1,23 @@
+{# FETCH FTS PARSER NAME Statement #}
+{% if pid %}
+SELECT
+    p.prsname AS name,
+    (
+    SELECT
+        nspname
+    FROM
+        pg_namespace
+    WHERE
+        oid = t.tmplnamespace
+    ) as schema
+FROM
+    pg_ts_parser p LEFT JOIN pg_description d
+    ON d.objoid=p.oid AND d.classoid='pg_ts_parser'::regclass
+WHERE
+    p.oid = {{pid}}::OID;
+{% endif %}
+
+{# DROP FTS PARSER Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH PARSER {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} {% if cascade %}CASCADE{%endif%};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/functions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/functions.sql
new file mode 100644
index 0000000..9597790
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/functions.sql
@@ -0,0 +1,58 @@
+{# FETCH start functions for FTS_PARSER #}
+{% if start %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    proargtypes='2281 23'
+ORDER BY proname;
+{% endif %}
+
+{# FETCH token functions for FTS_PARSER #}
+{% if token %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    proargtypes='2281 2281 2281'
+ORDER BY
+    proname;
+{% endif %}
+
+{# FETCH end functions for FTS_PARSER #}
+{% if end %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2278 and proargtypes='2281'
+ORDER BY
+    proname;
+{% endif %}
+
+{# FETCH lextype functions for FTS_PARSER #}
+{% if lextype %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2281 and proargtypes='2281'
+ORDER BY
+    proname;
+{% endif %}
+
+{# FETCH headline functions for FTS_PARSER #}
+{% if headline %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    proargtypes='2281 2281 3615'
+ORDER BY
+    proname;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/nodes.sql
new file mode 100644
index 0000000..b8eb522
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/nodes.sql
@@ -0,0 +1,13 @@
+{# FETCH FTS PARSER name statement #}
+SELECT
+    oid, prsname as name
+FROM
+    pg_ts_parser prs
+WHERE
+{% if scid %}
+    prs.prsnamespace = {{scid}}::OID
+{% elif pid %}
+    prs.oid = {{pid}}::OID
+{% endif %}
+
+ORDER BY name
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/properties.sql
new file mode 100644
index 0000000..a79677d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/properties.sql
@@ -0,0 +1,30 @@
+{# FETCH properties for FTS PARSER #}
+SELECT
+    prs.oid,
+    prs.prsname as name,
+    prs.prsstart,
+    prs.prstoken,
+    prs.prsend,
+    prs.prslextype,
+    prs.prsheadline,
+    description,
+    prs.prsnamespace AS schema
+FROM
+    pg_ts_parser prs
+    LEFT OUTER JOIN pg_description des
+ON
+    (
+    des.objoid=prs.oid
+    AND des.classoid='pg_ts_parser'::regclass
+    )
+WHERE
+{% if scid %}
+    prs.prsnamespace = {{scid}}::OID
+{% elif name %}
+    prs.prsname = {{name|qtLiteral}}
+{% endif %}
+{% if pid %}
+    AND prs.oid = {{pid}}::OID
+{% endif %}
+ORDER BY
+    prs.prsname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/schema.sql
new file mode 100644
index 0000000..29ddaba
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/schema.sql
@@ -0,0 +1,19 @@
+{# FETCH statement for SCHEMA name #}
+{% if data.schema %}
+SELECT
+    nspname
+FROM
+    pg_namespace
+WHERE
+    oid = {{data.schema}}::OID
+
+{% elif data.id %}
+SELECT
+    nspname
+FROM
+    pg_namespace nsp
+    LEFT JOIN pg_ts_parser prs
+    ON prs.prsnamespace = nsp.oid
+WHERE
+    prs.oid = {{data.id}}::OID
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/sql.sql
new file mode 100644
index 0000000..2fa11e6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/sql.sql
@@ -0,0 +1,46 @@
+{# Reverse engineered sql for FTS PARSER #}
+{% if pid and scid %}
+SELECT
+    array_to_string(array_agg(sql), E'\n\n') as sql
+FROM
+    (
+    SELECT
+        E'-- Text Search Parser: ' || nspname || E'.' || prs.prsname ||
+        E'\n\n-- DROP TEXT SEARCH PARSER ' || nspname || E'.' || prs.prsname ||
+        E'\n\nCREATE TEXT SEARCH PARSER ' || nspname || E'.' ||  prs.prsname || E' (\n' ||
+        E'    START = ' || prs.prsstart || E',\n' ||
+        E'    GETTOKEN = ' || prs.prstoken || E',\n' ||
+        E'    END = ' || prs.prsend || E',\n' ||
+        E'    LEXTYPES = ' || prs.prslextype ||
+        CASE
+            WHEN prs.prsheadline != '-'::regclass THEN E',\n    HEADLINE = ' || prs.prsheadline
+            ELSE '' END || E'\n);' ||
+        CASE
+            WHEN description IS NOT NULL THEN
+                E'\n\nCOMMENT ON TEXT SEARCH TEMPLATE ' || nspname || E'.' || prs.prsname ||
+                E' IS ' || pg_catalog.quote_literal(description) || E';'
+            ELSE ''  END as sql
+    FROM
+        pg_ts_parser prs
+    LEFT JOIN (
+        SELECT
+            des.description as description,
+            des.objoid as descoid
+        FROM
+            pg_description des
+        WHERE
+            des.objoid={{pid}}::OID AND des.classoid='pg_ts_parser'::regclass
+    ) a ON (a.descoid = prs.oid)
+    LEFT JOIN (
+        SELECT
+            nspname,
+            nsp.oid as noid
+        FROM
+            pg_namespace nsp
+        WHERE
+            oid = {{scid}}::OID
+    ) b ON (b.noid = prs.prsnamespace)
+WHERE
+    prs.oid={{pid}}::OID
+) as c;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/update.sql
new file mode 100644
index 0000000..206575d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/templates/fts_parser/sql/9.1_plus/update.sql
@@ -0,0 +1,22 @@
+{# UPDATE statement for FTS PARSER #}
+{% if data %}
+{% if data.name and data.name != o_data.name %}
+ALTER TEXT SEARCH PARSER {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}}
+    RENAME TO {{data.name}};
+{% endif %}
+
+{#in case of rename, use new fts template name #}
+{% if data.name and data.name != o_data.name %}
+{% set name = data.name %}
+{% else %}
+{% set name = o_data.name %}
+{% endif %}
+{% if data.schema and data.schema != o_data.schema %}
+ALTER TEXT SEARCH PARSER {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    SET SCHEMA {{data.schema}};
+{% endif %}
+{% if data.description != o_data.description %}
+COMMENT ON TEXT SEARCH PARSER {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    IS {{ data.description|qtLiteral }};
+{% endif %}
+{% endif %}
\ No newline at end of file

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Ashesh Vashi <[email-masked]>
Date: Mon, 4 Apr 2016 13:24:03 +0530

On Mon, Mar 28, 2016 at 2:32 PM, Sanket Mehta <[email-masked]
> wrote:

> Please do review it and provide the comments.
Hi Sanket,

Thanks for the patch.
Please find my review comments.

* 'current_app' has been imported but not used.
* Few variables are assigned, but not used further.
  one of the example: "res = []" (fts_parser/__init__py line#271
* 'gone' is used, but - not imported.
* Do not require __init__(...) function in the 'FtsParserModule' class, as
it does not do anything here.
* Load the module with the database (not, schema), as it may be require to
show in dependencies list in the database.
* Some of the lines/comments are going beyond the line length limit (i.e.
more than 80 character per length).
* Please add comments for all the methods in FtsParserView.
* Do not need the URL routes for get_start, get_token, get_token,
get_headline with id, as it does not use the fts parse id. Declare these
URL-routes without id  for these methods.
* Create separate templates for each methods instead of club them together
for the above methods.
* HTTP method GET implies for getting/fetching the information/data from
the server. Please remove the 'get_' from the above methods,
* Inline comments for __init__ method (FtsParserView class) is missing.
* Do not need to override the 'module_js' method, it has already been
implemented in PGChildNodeView class.
* Please fix the correctness of the comments for all the methods. Avoid
copy/paste from other modules.
* Check for the version before setting the template_path variable in
check_precondition method is not required.
* Check the existence of the node/object before assuming, it is available
(otherwise - return with 'gone') in node, and properties method. SQL may
not fail, but - no of records returned will be 0 (zero).
* Please test the module on Python 3 too.
* Use generate_browser_node from the 'update' method after successful
operation, while generating the result.
* Do not catch exception (if not required) (i.e. in 'update' method, you
will not be able to catch the actual issue in that case). Please remove all
unwanted exceptions.
* Log the exception with the application, whenever we catch them.

Note:
I've not yet tested the patch.
These are the review comments from the python code only.
You may also want to look at the javascript module before sending for
review again.
(i.e. code should be wrapped after the line #79.)

--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com/>

*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi>

---

From: Sanket Mehta <[email-masked]>
Date: Thu, 7 Apr 2016 19:57:22 +0530

Hi,

PFA the revised patch.
response is inline.

Please do review the patch and revert with comments if any.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Mon, Apr 4, 2016 at 1:24 PM, Ashesh Vashi <[email-masked]>
wrote:

> On Mon, Mar 28, 2016 at 2:32 PM, Sanket Mehta <
> [email-masked]> wrote:
>
>> Hi,
>>
Fixed (it is used now for logging)

Unused variables are removed

Fixed

> it does not do anything here.
Removed

> show in dependencies list in the database.
Fixed

> more than 80 character per length).
Fixed

Fixed

> URL-routes without id  for these methods.
Fixed

> for the above methods.
Ignored as discussed with Ashesh.

> the server. Please remove the 'get_' from the above methods,
Fixed

> * Inline comments for __init__ method (FtsParserView class) is missing.
Fixed

> implemented in PGChildNodeView class.
Fixed

> copy/paste from other modules.
Fixed

> check_precondition method is not required.
Ignored as per discussion with Ashesh.

> not fail, but - no of records returned will be 0 (zero).
Fixed

> * Please test the module on Python 3 too.
Fixed

> operation, while generating the result.
Fixed

> will not be able to catch the actual issue in that case). Please remove all
Fixed

> * Log the exception with the application, whenever we catch them.
Fixed

> (i.e. code should be wrapped after the line #79.)
Fixed

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py
new file mode 100644
index 0000000..4f625b4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/__init__.py
@@ -0,0 +1,858 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines views for management of Fts Parser node"""
+
+import json
+from flask import render_template, request, jsonify, current_app
+from flask.ext.babel import gettext as _
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.browser.server_groups.servers.databases.schemas.utils import SchemaChildModule
+from pgadmin.browser.server_groups.servers.databases import DatabaseModule
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class FtsParserModule(SchemaChildModule):
+    """
+     class FtsParserModule(SchemaChildModule)
+
+        A module class for FTS Parser node derived from SchemaChildModule.
+
+    Methods:
+    -------
+    * get_nodes(gid, sid, did, scid)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for FTS Parser, when any of the schema node is
+        initialized.
+    """
+    NODE_TYPE = 'fts_parser'
+    COLLECTION_LABEL = _('FTS Parsers')
+
+    def get_nodes(self, gid, sid, did, scid):
+        """
+        Generate the collection node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        yield self.generate_browser_collection_node(scid)
+
+    @property
+    def node_inode(self):
+        """
+        Override the property to make the node as leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for fts template, when any of the schema node is
+        initialized.
+        """
+        return DatabaseModule.NODE_TYPE
+
+
+blueprint = FtsParserModule(__name__)
+
+
+class FtsParserView(PGChildNodeView):
+    """
+    class FtsParserView(PGChildNodeView)
+
+        A view class for FTS Parser node derived from PGChildNodeView.
+        This class is responsible for all the stuff related to view
+        like create/update/delete FTS Parser, showing properties of node,
+        showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the FtsParserView and it's base view.
+
+    * module_js()
+      - This property defines (if javascript) exists for this node.
+        Override this property for your own logic
+
+    * check_precondition()
+      - 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
+
+    * list()
+      - This function is used to list all the  nodes within that collection.
+
+    * nodes()
+      - This function will used to create all the child node within that
+        collection.
+      - Here it will create all the FTS Parser nodes.
+
+    * properties(gid, sid, did, scid, pid)
+      - This function will show the properties of the selected FTS Parser node
+
+    * create(gid, sid, did, scid)
+      - This function will create the new FTS Parser object
+
+    * update(gid, sid, did, scid, pid)
+      - This function will update the data for the selected FTS Parser node
+
+    * delete(self, gid, sid, did, scid, pid):
+      - This function will drop the FTS Parser object
+
+    * msql(gid, sid, did, scid, pid)
+      - This function is used to return modified SQL for
+        selected FTS Parser node
+
+    * get_sql(data, pid)
+      - This function will generate sql from model data
+
+    * get_start(self, gid, sid, did, scid, pid)
+      - This function will fetch start functions list for ftp parser
+
+    * get_token(self, gid, sid, did, scid, pid)
+      - This function will fetch token functions list for ftp parser
+
+    * get_end(self, gid, sid, did, scid, pid)
+      - This function will fetch end functions list for ftp parser
+
+    * get_lextype(self, gid, sid, did, scid, pid)
+      - This function will fetch lextype functions list for ftp parser
+
+    * get_headline(self, gid, sid, did, scid, pid)
+      - This function will fetch headline functions list for ftp parser
+
+    * sql(gid, sid, did, scid, pid):
+      - This function will generate sql to show it in sql pane for the selected
+        FTS Parser node.
+
+    * get_type():
+      - This function will fetch all the types for source and
+        target types select control.
+
+    * dependents(gid, sid, did, scid, pid):
+      - This function get the dependents and return ajax response for
+        Fts Parser node.
+
+    * dependencies(self, gid, sid, did, scid, pid):
+      - This function get the dependencies and return ajax response for
+        FTS Parser node.
+
+    """
+
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'},
+        {'type': 'int', 'id': 'scid'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'pid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'children': [{
+            'get': 'children'
+        }],
+        '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'}],
+        'start_functions': [{'get': 'start_functions'}, {'get': 'start_functions'}],
+        'token_functions': [{'get': 'token_functions'}, {'get': 'token_functions'}],
+        'end_functions': [{'get': 'end_functions'}, {'get': 'end_functions'}],
+        'lextype_functions': [{'get': 'lextype_functions'}, {'get': 'lextype_functions'}],
+        'headline_functions': [{'get': 'headline_functions'}, {'get': 'headline_functions'}],
+    })
+
+    def _init_(self, **kwargs):
+        """
+        Method is used to initialize the FtsParserView and it's base view.
+
+        Args:
+            *args:
+            **kwargs:
+        """
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+        super(FtsParserView, self).__init__(**kwargs)
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    _(
+                        "Connection to the server has been lost!"
+                    )
+                )
+            # we will set template path for sql scripts depending upon server version
+            ver = self.manager.version
+            if ver >= 90100:
+                self.template_path = 'fts_parser/sql/9.1_plus'
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                scid=scid
+              )
+        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, scid):
+        res = []
+        sql = render_template(
+                "/".join([self.template_path, 'nodes.sql']),
+                scid=scid
+              )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-fts_parser"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, scid, pid):
+        sql = render_template(
+                "/".join([self.template_path, 'nodes.sql']),
+                pid=pid
+              )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        if len(rset['rows']) == 0:
+            return gone(_("""
+                Could not find the fts parser node.
+                """))
+
+        for row in rset['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                        row['oid'],
+                        did,
+                        row['name'],
+                        icon="icon-fts_parser"
+                    ),
+                status=200
+            )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, pid):
+        sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                scid=scid,
+                pid=pid
+              )
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return gone(_("""
+                Could not find the fts parser node.
+                """))
+
+        return ajax_response(
+            response=res['rows'][0],
+            status=200
+        )
+
+    @check_precondition
+    def create(self, gid, sid, did, scid):
+        """
+        This function will creates new the fts_parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+
+        # Mandatory fields to create a new fts parser
+        required_args = [
+            'prsstart',
+            'prstoken',
+            'prsend',
+            'prslextype',
+            'schema',
+            '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=_(
+                        "Couldn't find the required parameter (%s)." % arg
+                    )
+                )
+        try:
+            # Fetch schema name from schema oid
+            sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data,
+                    conn=self.conn,
+                  )
+
+            status, schema = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=schema)
+
+            # replace schema oid with schema name before passing to create.sql
+            # to generate proper sql query
+            new_data = data.copy()
+            new_data['schema'] = schema
+            sql = render_template(
+                    "/".join([self.template_path, 'create.sql']),
+                    data=new_data,
+                    conn=self.conn,
+                  )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # we need fts_parser id to to add object in tree at browser,
+            # below sql will give the same
+            sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                name=data['name']
+            )
+            status, pid = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=pid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    pid,
+                    did,
+                    data['name'],
+                    icon="icon-fts_parser"
+                )
+            )
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, scid, pid):
+        """
+        This function will update text search parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts parser id
+        """
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+
+        # Fetch sql query to update fts parser
+        sql = self.get_sql(gid, sid, did, scid, data, pid)
+        if sql and sql.strip('\n') and sql.strip(' '):
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            if pid is not None:
+                sql = render_template(
+                        "/".join([self.template_path, 'properties.sql']),
+                        pid=pid,
+                        scid=scid
+                      )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if len(res['rows']) == 0:
+                    return gone(_("""
+                        Could not find the fts parser node to update.
+                    """))
+
+                data = res['rows'][0]
+                return jsonify(
+                        node=self.blueprint.generate_browser_node(
+                        pid,
+                        did,
+                        data['name'],
+                        icon="icon-fts_parser"
+                    )
+                )
+        else:
+            return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id':   pid,
+                        'scid': scid,
+                        'sid':  sid,
+                        'gid':  gid,
+                        'did':  did
+                    }
+                )
+
+    @check_precondition
+    def delete(self, gid, sid, did, scid, pid):
+        """
+        This function will drop the fts_parser object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name for Parser from pid
+            sql = render_template(
+                    "/".join([self.template_path, 'delete.sql']),
+                    pid=pid
+                  )
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            if len(res['rows']) == 0:
+                return gone(_("""
+                    Could not find the fts parser node.
+                    """))
+
+            # Drop fts Parser
+            result = res['rows'][0]
+            sql = render_template(
+                    "/".join([self.template_path, 'delete.sql']),
+                    name=result['name'],
+                    schema=result['schema'],
+                    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=_("FTS Parser dropped"),
+                data={
+                    'id': pid,
+                    'sid': sid,
+                    'gid': gid,
+                    'did': did,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, scid, pid=None):
+        """
+        This function returns modified SQL
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        data = request.args
+
+        # Fetch sql query for modified data
+        sql = self.get_sql(gid, sid, did, scid, data, pid)
+
+        if isinstance(sql, str) and sql and sql.strip('\n') and sql.strip(' '):
+            return make_json_response(
+                data=sql,
+                status=200
+            )
+        else:
+            return make_json_response(
+                data="--modified SQL",
+                status=200
+            )
+
+    def get_sql(self, gid, sid, did, scid, data, pid=None):
+        """
+        This function will return SQL for model data
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        try:
+            # Fetch sql for update
+            if pid is not None:
+                sql = render_template(
+                        "/".join([self.template_path, 'properties.sql']),
+                        pid=pid,
+                        scid=scid
+                      )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if len(res['rows']) == 0:
+                    return gone(_("""
+                        Could not find the fts parser node.
+                    """))
+
+                old_data = res['rows'][0]
+
+                # If user has changed the schema then fetch new schema directly
+                # using its oid otherwise fetch old schema name with parser oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data)
+
+                status, new_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=new_schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                if 'schema' in new_data:
+                    new_data['schema'] = new_schema
+
+                # Fetch old schema name using old schema oid
+                sql = render_template(
+                        "/".join([self.template_path, 'schema.sql']),
+                        data=old_data
+                      )
+
+                status, old_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=old_schema)
+
+                # Replace old schema oid with old schema name
+                old_data['schema'] = old_schema
+
+                sql = render_template(
+                        "/".join([self.template_path, 'update.sql']),
+                        data=new_data,
+                        o_data=old_data
+                      )
+                # Fetch sql query for modified data
+            else:
+                # Fetch schema name from schema oid
+                sql = render_template(
+                        "/".join([self.template_path, 'schema.sql']),
+                        data=data
+                      )
+
+                status, schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                new_data['schema'] = schema
+
+                if 'prsstart' in new_data and \
+                        'prstoken' in new_data and \
+                        'prsend' in new_data and \
+                        'prslextype' in new_data and \
+                        'name' in new_data and \
+                        'schema' in new_data:
+                    sql = render_template(
+                            "/".join([self.template_path,'create.sql']),
+                            data=new_data,
+                            conn=self.conn
+                          )
+                else:
+                    sql = "-- incomplete definition"
+            return str(sql.strip('\n'))
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def start_functions(self, gid, sid, did, scid):
+        """
+        This function will return start functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              start=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at start select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def token_functions(self, gid, sid, did, scid):
+        """
+        This function will return token functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              token=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at token select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def end_functions(self, gid, sid, did, scid):
+        """
+        This function will return end functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              end=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at end select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def lextype_functions(self, gid, sid, did, scid):
+        """
+        This function will return lextype functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              lextype=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at lextype select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def headline_functions(self, gid, sid, did, scid):
+        """
+        This function will return headline functions list for fts Parser
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              headline=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at headline select control while creating a new fts parser
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, pid):
+        """
+        This function will reverse generate sql for sql panel
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param pid: fts tempate id
+        """
+        try:
+            sql = render_template(
+                "/".join([self.template_path, 'sql.sql']),
+                pid=pid,
+                scid=scid,
+                conn=self.conn
+            )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered \
+                        Query for the FTS Parser!\n{0}"
+                    ).format(res)
+                )
+
+            if res is None:
+                return gone(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered \
+                        Query for FTS Parser node!"
+                    )
+                )
+
+            return ajax_response(response=res)
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def dependents(self, gid, sid, did, scid, pid):
+        """
+        This function get the dependents and return ajax response
+        for the FTS Parser node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            pid: FTS Parser ID
+        """
+        dependents_result = self.get_dependents(self.conn, pid)
+        return ajax_response(
+                response=dependents_result,
+                status=200
+                )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, scid, pid):
+        """
+        This function get the dependencies and return ajax response
+        for the FTS Parser node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            pid: FTS Tempalte ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, pid)
+        return ajax_response(
+                response=dependencies_result,
+                status=200
+                )
+
+FtsParserView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/coll-fts_parser.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/img/coll-fts_parser.png
new file mode 100644
index 0000000000000000000000000000000000000000..340ec6b7fbdcf1702f66fe2c833fcdcd512bb141
GIT binary patch
literal 620
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47+C{+LR^8|m3!MN7a1f>VVS)(
zuW6md$rp<r{6BdA|NguGcHjQD?dIPN*Z-`z`g_TNr=9g{4YFrR-1xeE_4R_vRqAES
z71C!2EI5^vGfy;Uw$zRP8?XFZe&OHzvwx<a`rUWzSIeOvmHWQu@BdbC?0v`FZ3XxL
z@4NVK(QcLNhkw@0O`Wt$`Rbm@lMjC_Nt?lc``?z+e<q*!)p_J+-GT4LhdWo_On!X#
z$ksbYHrz<Md$@hY^*<|*{%mSo=3lqeW5?I{0}_v}99lJFSJ1Sbz7MW0KdJou%!iqW
zew6R}n!NRM*oIGDyV|B)Pr7sS@UokS7hO-fb)a!s^G4I7UuzD1E!_V(bI+%w1CwVT
zH@UUHZ(jX+&1wblhrobfED7=pW^j0RBMr#mEbxdd2GSm2>~=ES4#=42>Eak7aXC5R
z07H+@8G!>wIG%0ZuxVp)K~dr751&46ZeU?yX>D2P<>KPVbS5doYsS%wQw$bsPh`-V
zIAzkbiBl(s2ZRVEBm|_Tg$0HN2M1pdxP0YO8r!RjiPx@PHa9RaGBY$aX6Mu}j*f_m
zynW-=O=}CQMiz;ZIk7Q!@7UVx-BVkm>cDiW&g&mzqvJtE#f6Ed1g>*C+*Leq$bp4{
z;lM(nj5%k`L4Hv!ag8WRNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KrrHLkRt5(1
t-s!DE(U6;;l9^Ts(O_T+)&Nv(Vr5_k(Qs;d=o+8~22WQ%mvv4FO#s2@A-Vtn

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..b6503f6
@@ -0,0 +1,193 @@
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin',
+         'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+          name: 'create_fts_parser_on_coll', node: 'coll-fts_parser',
+          icon: 'wcTabIcon icon-fts_parser', data: {action: 'create'},
+          module: this
+          prsstart: undefined,      // Start function for fts parser
+          prstoken: undefined,       // Token function for fts parser
+          prsend: undefined,        // End function for fts parser
+          prslextype: undefined,    // Lextype function for fts parser
+          prsheadline: undefined    // Headline function for fts parse
+            pgAdmin.Browser.Node.Model.prototype.initialize.apply(this,
+                                                                arguments
+                                                            );
+          id: 'prsstart', label: '{{ _('Start Function')}}',
+          control: 'node-ajax-options', url: 'start_functions',
+          group: '{{ _('Definition') }}'
+          id: 'prstoken', label: '{{ _('Gettoken Function')}}',
+          control: 'node-ajax-options', url: 'token_functions',
+          group: '{{ _('Definition') }}'
+          id: 'prsend', label: '{{ _('End Function')}}',
+          control: 'node-ajax-options', url: 'end_functions',
+          group: '{{ _('Definition') }}'
+          id: 'prslextype', label: '{{ _('Lextypes Function')}}',
+          control: 'node-ajax-options', url: 'lextype_functions',
+          group: '{{ _('Definition') }}'
+          id: 'prsheadline', label: '{{ _('Headline Function')}}',
+          control: 'node-ajax-options', url: 'headline_functions',
+          group: '{{ _('Definition') }}'
+         * start, token, end, lextype functions and schema, if any one of them is not specified
+          if (_.isUndefined(name) ||
+                _.isNull(name) ||
+                String(name).replace(/^\s+|\s+$/g, '') == '') {
+          else if (_.isUndefined(start) ||
+                    _.isNull(start) ||
+                    String(start).replace(/^\s+|\s+$/g, '') == '') {
+          else if (_.isUndefined(token) ||
+                    _.isNull(token) ||
+                    String(token).replace(/^\s+|\s+$/g, '') == '') {
+          else if (_.isUndefined(end) ||
+                    _.isNull(end) ||
+                    String(end).replace(/^\s+|\s+$/g, '') == '') {
+          else if (_.isUndefined(lextype) ||
+                    _.isNull(lextype) ||
+                    String(lextype).replace(/^\s+|\s+$/g, '') == '') {
+          else if (_.isUndefined(schema) ||
+                    _.isNull(schema) ||
+                    String(schema).replace(/^\s+|\s+$/g, '') == '') {
index 0000000..7361105
+        oid = p.prsnamespace
index 0000000..ac29d99
+{% if "description" in data and data.description != o_data.description %}

---

From: Dave Page <[email-masked]>
Date: Thu, 7 Apr 2016 21:01:41 +0100

Thanks - committed with one minor bug:

- Renaming a parser doesn't update the label on the treeview node.

I've added a card to our internal kanban chart for this - please submit a
patch to fix ASAP.

I also fixed some minor string and SQL related issues.

On Thu, Apr 7, 2016 at 3:27 PM, Sanket Mehta <[email-masked]>
wrote:

> On Mon, Apr 4, 2016 at 1:24 PM, Ashesh Vashi <
> [email-masked]> wrote:
>

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company