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 (pgadmin-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

Reply via email to