Subject: PATCH: FTS Dictionary

From: Sanket Mehta <[email-masked]>
Date: Thu, 14 Apr 2016 20:11:09 +0530

Hi,

PFA patch for FTS Dictionaries.
Please do review it and let me know in case of any issues.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py
new file mode 100644
index 0000000..3f7f278
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py
@@ -0,0 +1,818 @@
+##########################################################################
+#
+# 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 Dictionary node"""
+
+import json
+from flask import render_template, make_response, current_app, request, jsonify
+from flask.ext.babel import gettext as _
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error, gone
+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 FtsDictionaryModule(SchemaChildModule):
+    """
+     class FtsDictionaryModule(SchemaChildModule)
+
+        A module class for FTS Dictionary node derived from SchemaChildModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the FtsDictionaryModule 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 Dictionary, when any of the schema
+      node is initialized.
+    """
+    NODE_TYPE = 'fts_dictionary'
+    COLLECTION_LABEL = _('FTS Dictionaries')
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+        self.manager = None
+        super(FtsDictionaryModule, 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 = FtsDictionaryModule(__name__)
+
+
+class FtsDictionaryView(PGChildNodeView):
+    """
+    class FtsDictionaryView(PGChildNodeView)
+
+        A view class for FTS Dictionary node derived from PGChildNodeView.
+        This class is responsible for all the stuff related to view like
+        create/update/delete FTS Dictionary,
+        showing properties of node, showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the FtsDictionaryView 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
+
+    * tokenize_options(self, option_value):
+    -   This function will tokenize the string stored in database
+        e.g. database store the value as below
+        key1=value1, key2=value2, key3=value3, ....
+        This function will extract key and value from above string
+
+    * list()
+      - This function is used to list all the  nodes within that collection.
+
+    * nodes()
+      - This function will be used to create all the child node within collection.
+        Here it will create all the FTS Dictionary nodes.
+
+    * node()
+      - This function will be used to create a node given its oid
+        Here it will create the FTS Template node based on its oid
+
+    * properties(gid, sid, did, scid, dcid)
+      - This function will show the properties of the selected FTS Dictionary node
+
+    * create(gid, sid, did, scid)
+      - This function will create the new FTS Dictionary object
+
+    * update(gid, sid, did, scid, dcid)
+      - This function will update the data for the selected FTS Dictionary node
+
+    * delete(self, gid, sid, did, scid, dcid):
+      - This function will drop the FTS Dictionary object
+
+    * msql(gid, sid, did, scid, dcid)
+      - This function is used to return modified SQL for the selected node
+
+    * get_sql(data, dcid)
+      - This function will generate sql from model data
+
+    * sql(gid, sid, did, scid, dcid):
+      - This function will generate sql to show in sql pane for node.
+
+    * fetch_templates():
+      - This function will fetch all templates related to node
+
+    * dependents(gid, sid, did, scid, dcid):
+      - This function get the dependents and return ajax response for the node.
+
+    * dependencies(self, gid, sid, did, scid, dcid):
+      - This function get the dependencies and return ajax response for 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': 'dcid'}
+    ]
+
+    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'}],
+        'fetch_templates': [{'get': 'fetch_templates'},
+                            {'get': 'fetch_templates'}],
+    })
+
+    def _init_(self, **kwargs):
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+        super(FtsDictionaryView, self).__init__(**kwargs)
+
+    def module_js(self):
+        """
+        Load JS file (fts_dictionary.js) for this module.
+        """
+        return make_response(
+            render_template(
+                "fts_dictionary/js/fts_dictionary.js",
+                _=_
+            ),
+            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(
+                    _("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_dictionary/sql/9.1_plus'
+            return f(*args, **kwargs)
+
+        return wrap
+
+    def tokenize_options(self, option_value):
+        """
+        This function will tokenize the string stored in database
+        e.g. database store the value as below
+        key1=value1, key2=value2, key3=value3, ....
+        This function will extract key and value from above string
+
+        Args:
+            option_value: key value option/value pair read from database
+        """
+        if option_value is not None:
+            option_str = option_value.split(',')
+            options = []
+            for fdw_option in option_str:
+                k, v = fdw_option.split('=', 1)
+                options.append({'option': k, 'value': v})
+            return options
+
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        """
+        List all FTS Dictionary nodes.
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+        """
+
+        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)
+
+        for row in res['rows']:
+            if row['options'] is not None:
+                row['options'] = self.tokenize_options(row['options'])
+
+        return ajax_response(
+            response=res['rows'],
+            status=200
+        )
+
+    @check_precondition
+    def nodes(self, gid, sid, did, scid):
+        """
+        Return all FTS Dictionaries to generate nodes.
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+        """
+
+        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_dictionary"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, scid, dcid):
+        """
+        Return FTS Dictionary node to generate node
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+            dcid: fts dictionary id
+        """
+
+        sql = render_template(
+            "/".join([self.template_path, 'nodes.sql']),
+            dcid=dcid
+        )
+        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 dictionary node.
+                """))
+
+        for row in rset['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                        row['oid'],
+                        did,
+                        row['name'],
+                        icon="icon-fts_dictionary"
+                    ),
+                status=200
+            )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, dcid):
+        """
+        Show properties of FTS Dictionary node
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+            dcid: fts dictionary id
+        """
+
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid,
+            dcid=dcid
+        )
+        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 dictionary node.
+                """))
+
+        if res['rows'][0]['options'] is not None:
+            res['rows'][0]['options'] = self.tokenize_options(res['rows'][0]['options'])
+
+        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 Dictionary object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+
+        # Mandatory fields to create a new FTS Dictionary
+        required_args = [
+            'template',
+            '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 dcid 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, dcid = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=dcid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    dcid,
+                    did,
+                    data['name'],
+                    icon="icon-fts_dictionary"
+                )
+            )
+        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, dcid):
+        """
+        This function will update FTS Dictionary object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param dcid: fts dictionary id
+        """
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+
+        # Fetch sql query to update fts dictionary
+        sql = self.get_sql(gid, sid, did, scid, data, dcid)
+        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)
+
+                if dcid is not None:
+                    sql = render_template(
+                        "/".join([self.template_path, 'properties.sql']),
+                        dcid=dcid,
+                        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 dictionary node to update.
+                    """))
+
+                data = res['rows'][0]
+                return jsonify(
+                        node=self.blueprint.generate_browser_node(
+                            dcid,
+                            did,
+                            data['name'],
+                            icon="icon-fts_dictionary"
+                        )
+                )
+            # In case FTS Dictionary node is not present
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id': dcid,
+                        '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 delete(self, gid, sid, did, scid, dcid):
+        """
+        This function will drop the FTS Dictionary object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param dcid: FTS Dictionary 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 FTS Dictionary from dcid
+            sql = render_template("/".join([self.template_path, 'delete.sql']),
+                                  dcid=dcid)
+            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 dictionary node to delete.
+                    """))
+
+            # Drop FTS Dictionary
+            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 Dictionary dropped"),
+                data={
+                    'id': dcid,
+                    '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, dcid=None):
+        """
+        This function returns modified SQL
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param dcid: FTS Dictionary id
+        """
+        #data = request.args
+        data = {}
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v)
+            except ValueError:
+                data[k] = v
+
+        # Fetch sql query for modified data
+        sql = self.get_sql(gid, sid, did, scid, data, dcid)
+
+        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, dcid=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 dcid: fts dictionary id
+        """
+        try:
+            # Fetch sql for update
+            if dcid is not None:
+                sql = render_template(
+                    "/".join([self.template_path, 'properties.sql']),
+                    dcid=dcid,
+                    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 dictionary 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 using its 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 'template' 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 fetch_templates(self, gid, sid, did, scid):
+        """
+        This function will return templates list for FTS Dictionary
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        # Fetch last system oid
+        datlastsysoid = self.manager.db_info[did]['datlastsysoid']
+
+        sql = render_template("/".join([self.template_path, 'templates.sql']),
+                              template=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 template control while creating a new FTS Dictionary
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            if row['schemaoid'] > datlastsysoid :
+                row['tmplname'] = row['nspname'] + '.' + row['tmplname']
+
+            res.append({'label': row['tmplname'],
+                        'value': row['tmplname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, dcid):
+        """
+        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 dcid: FTS Dictionary id
+        """
+        try:
+            sql = render_template(
+                "/".join([self.template_path, 'sql.sql']),
+                dcid=dcid,
+                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 Dictionary!\n{0}").format(
+                        res
+                    )
+                )
+
+            if res is None:
+                return gone(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered Query for FTS Dictionary 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, dcid):
+        """
+        This function get the dependents and return ajax response
+        for the FTS Dictionary node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            dcid: FTS Dictionary ID
+        """
+        dependents_result = self.get_dependents(self.conn, dcid)
+        return ajax_response(
+                response=dependents_result,
+                status=200
+                )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, scid, dcid):
+        """
+        This function get the dependencies and return ajax response
+        for the FTS Dictionary node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            dcid: FTS Dictionary ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, dcid)
+        return ajax_response(
+                response=dependencies_result,
+                status=200
+                )
+
+FtsDictionaryView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/img/coll-fts_dictionary.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/img/coll-fts_dictionary.png
new file mode 100644
index 0000000000000000000000000000000000000000..a97eb49af2861ce712def8db538f583301361c79
GIT binary patch
literal 362
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Q-Dv1E0CVF<)~EUstZR-?YlSk
zEKWVU+dg9Y4y(?Mfk(f(?)hlD{k_k@&n6q*Xs>*!wD_6ioX3LGA978)FObyF+IKg7
z{+^#%+eLt;FqQ=Q1v5B2yO9Rua29w(76WMyFm^kcZ3kq;d%8G=NL)@m@6Fesz~dUI
zVb91Ic;kQA)}wo7-1y>mZbj%T^I1KW)0EF9%>DjEHC>WPFgL%kG0EpeYme9)R$UKK
z-4nc=Z=O0Ztt?ZSaJ!S`l<5SAx6FoT&cFQpfA=oN`TJORtT57g541|P#5JNMC9x#c
zD!C{XNHG{07#ipr8tEDsh8P)GnHXD{m}ncAS{WEv%v6;_(U6;;l9^Ts(O_T+)&Np%
YWnc!;aB6z!8lVOSPgg&ebxsLQ0H%k2tN;K2

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/img/fts_dictionary.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/img/fts_dictionary.png
new file mode 100644
index 0000000000000000000000000000000000000000..55a880b7a0e14db1433b90f559beefdd1d3071fb
GIT binary patch
literal 634
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47zG1-LR^8|wsnW4DpxJsebRK%
zRpW(MZPwg(+xy9R_eYDR*Nm3jwq1MQbN?sTy&o;+ol(u}SI+I%pL1S&!A<8iH$C=!
za^CS;J%56B`zD2~KG}?3o#juICZCzL<AlYu{kF@mJM8>mI{%_lcAwUi6FRG3YAk=D
zvgEnk^s8E(+l(geHCueuV)I+Qxt9&rzS3UxQhoUg<t5ML7e1Aq_e5gSW%ahL+C97V
z=3Lfa^Ga*wOSNS$lomgeTkuqB?h~=uk3{<~DK&3a>DZw*?Sksk=ZcG-$u4**Irp*n
z#7mRsq{}sIl4;r|JMo;%{3nu=E>E15b$pZCh5go&)$2rSw}`bJ>Ytu-be-4P-S&$%
zg@{(IKef$i{<`qSHHYS|iJEjwaUU>H7)yfuf*Bm1-ADs+I14-?i-EKU7`vU!wgWO2
zc)B=-NL<c6ca`r@fB?&d+nRYt@7}!|+r9hqe|e@m7aWAY<@%+Wd!M+U$Pgx@dTITh
zMLz2SR<ST%ky_h#S-MBQ@np`%BD39xUpCEdI{x}I`<sIS7D7v9k38y3(VBbm*{9Y=
z(GgQlP0d<+FJs#+*@it!Z@=bmTl|sb!OKXo%QtH_vV^_n{duBn`|*brR+mM!#6Mh@
zKEQGRUgL59C9;8k=Bh7^5_nkP@=+P+R@D;Mh?11Vl2ohYqEsNoU}RuuplfKPYhV~+
zWME}tY-M7iZD49;U|=y*RSrc%ZhlH;S|vn-fhAZ2NVS!L8AQXW>7i?Y8W=oX{an^L
HB{Ts5+LaYu

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js
new file mode 100644
index 0000000..7427bd7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js
@@ -0,0 +1,183 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin',
+         'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+  // Extend the browser's node model class to create a option/value pair
+  var OptionLabelModel = pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          options: undefined,
+          value: undefined
+        },
+        // Define the schema for the Options
+        schema: [
+          {
+            id: 'option', label:'Option', type:'text', group: null,
+            cellHeaderClasses:'width_percent_50', editable: true
+          },{
+            id: 'value', label:'Value', type: 'text', group:null,
+            cellHeaderClasses:'width_percent_50', editable: true
+            },
+        ],
+        validate: function() {
+            // Clear any existing errors.
+            this.errorModel.clear()
+
+            if (_.isUndefined(this.get('option')) ||
+                String(this.get('option')).replace(/^\s+|\s+$/g, '') == '') {
+                var msg = '{{ _('Option can not be empty!') }}';
+                this.errorModel.set('option',msg);
+                return msg;
+            }
+            if (_.isUndefined(this.get('value')) ||
+                String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') {
+                var msg = '{{ _('Value can not be empty!') }}';
+                this.errorModel.set('value',msg);
+                return msg;
+            }
+            return null;
+        }
+    });
+
+  // Extend the collection class for FTS Dictionary
+  if (!pgBrowser.Nodes['coll-fts_dictionary']) {
+    var fts_dictionaries = pgAdmin.Browser.Nodes['coll-fts_dictionary'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'fts_dictionary',
+        label: '{{ _('FTS Dictionaries') }}',
+        type: 'coll-fts_dictionary',
+        columns: ['name', 'description']
+      });
+  };
+
+  // Extend the node class for FTS Dictionary
+  if (!pgBrowser.Nodes['fts_dictionary']) {
+    pgAdmin.Browser.Nodes['fts_dictionary'] = pgAdmin.Browser.Node.extend({
+      parent_type: ['schema', 'catalog'],
+      type: 'fts_dictionary',
+      canDrop: true,
+      canDropCascade: true,
+      label: '{{ _('FTS dictionaries') }}',
+      hasSQL: true,
+      hasDepends: true,
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Add context menus for FTS Dictionary
+        pgBrowser.add_menus([{
+          name: 'create_fts_dictionary_on_schema', node: 'schema', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{_('FTS Dictionary...')}}',
+          icon: 'wcTabIcon icon-fts_dictionary', data: {action: 'create'}
+          },{
+          name: 'create_fts_dictionary_on_coll', node: 'coll-fts_dictionary',
+          module: this, applies: ['object', 'context'],  priority: 4,
+          callback: 'show_obj_properties', category: 'create',
+          label: '{{ _('FTS Dictionary...') }}', data: {action: 'create'},
+          icon: 'wcTabIcon icon-fts_dictionary'
+          },{
+          name: 'create_fts_dictionary', node: 'fts_dictionary', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{_('FTS Dictionary...')}}',
+          icon: 'wcTabIcon icon-fts_dictionary', data: {action: 'create'}
+          }]);
+      },
+
+      // Defining backform model for FTS Dictionary node
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,        // FTS Dictionary name
+          owner: undefined,       // FTS Dictionary owner
+          description: undefined, // Comment on FTS Dictionary
+          schema: undefined,      // Schema name FTS dictionary belongs to
+          template: undefined,    // Template list for FTS dictionary node
+          options: undefined      // option/value pair list for FTS Dictionary
+        },
+        initialize: function(attrs, args) {
+          var isNew = (_.size(attrs) === 0);
+
+          if (isNew) {
+            var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
+            this.set({'owner': userInfo.name}, {silent: true});
+          }
+          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 dictionary
+        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: 'owner', label:'{{ _('Owner') }}', cell: 'string',
+          type: 'text', mode: ['properties', 'edit','create'], node: 'role',
+          control: Backform.NodeListByNameControl
+        },{
+          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: 'template', label: '{{ _('Template')}}',type: 'text',
+          disabled: function(m) { return !m.isNew(); }, url: 'fetch_templates',
+           group: '{{ _('Definition') }}',control: 'node-ajax-options'
+        },{
+          id: 'options', label: '{{ _('Option') }}', type: 'collection',
+          group: '{{ _('Options') }}', control: 'unique-col-collection',
+          model: OptionLabelModel, columns: ['option', 'value'],
+          uniqueCol : ['option'], mode: ['edit', 'create', 'properties'],
+          canAdd: true, canEdit: false,canDelete: true
+         }],
+
+        /*
+         * Triggers control specific error messages for dictionary name,
+         * template and schema, if any one of them is not specified
+         * while creating new fts dictionary
+         */
+        validate: function(keys){
+          var name = this.get('name');
+          var template = this.get('template');;
+          var schema = this.get('schema');
+
+          // Validate FTS Dictionary 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 template name
+          else if (_.isUndefined(template) || _.isNull(template) || String(template).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Template must be selected!') }}';
+            this.errorModel.set('template', msg);
+            return msg;
+          }
+
+          // Validate schema
+          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_dictionary'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql
new file mode 100644
index 0000000..cd1187c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql
@@ -0,0 +1,14 @@
+{# CREATE FTS DICTIONARY Statement #}
+{% if data and data.schema and data.name and data.template %}
+CREATE TEXT SEARCH DICTIONARY {{ conn|qtIdent(data.schema, data.name) }} (
+    TEMPLATE = data.template {% for variable in data.options %}
+    {% if "options" in variable and variable.options != '' %}
+    ,{{ conn|qtIdent(variable.options) }}={{variable.value|qtLiteral}}
+    {% endif %}
+    {% endfor %}
+);
+{# Description for FTS_DICTIONARY #}
+{% if data.description %}
+COMMENT ON TEXT SEARCH DICTIONARY {{ 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_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql
new file mode 100644
index 0000000..c344a7d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql
@@ -0,0 +1,23 @@
+{# FETCH FTS DICTIONARY NAME Statement #}
+{% if dcid %}
+SELECT
+    dict.dictname as name,
+    (
+    SELECT
+        nspname
+    FROM
+        pg_namespace
+    WHERE
+        oid = dict.dictnamespace
+    ) as schema
+FROM
+    pg_ts_dict dict LEFT OUTER JOIN pg_description des
+    ON (des.objoid=dict.oid AND des.classoid='pg_ts_dict'::regclass)
+WHERE
+    dict.oid = {{dcid}}::OID;
+{% endif %}
+
+{# DROP FTS DICTIOANRY Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH DICTIONARY {{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_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql
new file mode 100644
index 0000000..78abd0b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql
@@ -0,0 +1,13 @@
+{# FETCH FTS DICTIONARY name statement #}
+SELECT
+    oid, dictname as name
+FROM
+    pg_ts_dict dict
+WHERE
+{% if scid %}
+    dict.dictnamespace = {{scid}}::OID
+{% elif dcid %}
+    dict.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_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql
new file mode 100644
index 0000000..16e5644
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql
@@ -0,0 +1,24 @@
+{# FETCH properties for FTS DICTIONARY #}
+SELECT
+    dict.oid,
+    dict.dictname as name,
+    pg_get_userbyid(dict.dictowner) as owner,
+    t.tmplname as template,
+    dict.dictinitoption as options,
+    dict.dictnamespace as schema,
+    des.description
+FROM
+    pg_ts_dict dict
+    LEFT OUTER JOIN pg_ts_template t ON t.oid=dict.dicttemplate
+    LEFT OUTER JOIN pg_description des ON (des.objoid=dict.oid AND des.classoid='pg_ts_dict'::regclass)
+WHERE
+{% if scid %}
+    dict.dictnamespace = {{scid}}::OID
+{% elif name %}
+dict.dictname = {{name|qtLiteral}}
+{% endif %}
+{% if dcid %}
+    AND dict.oid = {{dcid}}::OID
+{% endif %}
+ORDER BY
+    dict.dictname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql
new file mode 100644
index 0000000..2a38e7f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/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_dict dict
+    ON dict.dictnamespace = nsp.oid
+WHERE
+    dict.oid = {{data.id}}::OID
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql
new file mode 100644
index 0000000..42537f6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql
@@ -0,0 +1,52 @@
+{# REVERSED ENGINEERED SQL FOR FTS DICTIONARY #}
+{% if dcid and scid %}
+SELECT
+    array_to_string(array_agg(sql), E'\n\n') as sql
+FROM
+    (
+    SELECT
+        E'-- Text Search Dictionary: ' || nspname || E'.' || dict.dictname ||
+        E'\n\n-- DROP TEXT SEARCH DICTIONARY ' || nspname || E'.' || dict.dictname ||
+        E'\n\nCREATE TEXT SEARCH DICTIONARY ' || nspname || E'.' ||  dict.dictname || E' (\n' ||
+        E'\tTEMPLATE = ' || template ||
+        CASE
+            WHEN dict.dictinitoption IS NOT NULL THEN E',\n\t' || dict.dictinitoption
+            ELSE ''
+        END ||
+        E'\n);' ||
+        CASE
+            WHEN description IS NOT NULL THEN
+                E'\n\nCOMMENT ON TEXT SEARCH TEMPLATE ' || nspname || E'.' || dict.dictname ||
+                E' IS ' || pg_catalog.quote_literal(description) || E';'
+            ELSE ''  END as sql
+    FROM
+        pg_ts_dict dict
+    LEFT JOIN(
+        SELECT
+            t.tmplname as template,
+            t.oid as oid
+        FROM
+            pg_ts_template t
+    ) d on d.oid = dict.dicttemplate
+    LEFT JOIN (
+        SELECT
+            des.description as description,
+            des.objoid as descoid
+        FROM
+            pg_description des
+        WHERE
+            des.objoid={{dcid}}::OID AND des.classoid='pg_ts_dict'::regclass
+    ) a ON (a.descoid = dict.oid)
+    LEFT JOIN (
+        SELECT
+            nspname,
+            nsp.oid as noid
+        FROM
+            pg_namespace nsp
+        WHERE
+            oid = {{scid}}::OID
+    ) b ON (b.noid = dict.dictnamespace)
+WHERE
+    dict.oid={{dcid}}::OID
+) as c;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql
new file mode 100644
index 0000000..d3270ba
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql
@@ -0,0 +1,11 @@
+{# FETCH templates for FTS DICTIONARY #}
+{% if template %}
+SELECT
+    tmplname,
+    nspname,
+    n.oid as schemaoid
+FROM
+    pg_ts_template JOIN pg_namespace n ON n.oid=tmplnamespace
+ORDER BY
+    tmplname
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql
new file mode 100644
index 0000000..1ef1eb4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql
@@ -0,0 +1,43 @@
+{# UPDATE statement for FTS DICTIONARY #}
+{% if data %}
+{% set name = o_data.name %}
+{% set schema = o_data.schema %}
+{% if data.name and data.name != o_data.name %}
+{% set name = data.name %}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}}
+    RENAME TO {{data.name}};
+{% endif %}
+{% if 'options' in data%}
+{% if'changed' in data.options %}
+{% for opt in data.options.changed %}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    ({{opt.option}}={{opt.value}});
+{% endfor %}
+{% endif %}
+{% if'added' in data.options%}
+{% for opt in data.options.added %}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    ({{opt.option}}={{opt.value}});
+{% endfor %}
+{% endif %}
+{% if'deleted' in data.options%}
+{% for opt in data.options.deleted %}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    ({{opt.option}});
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'owner' in data and data.owner != o_data.owner %}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    OWNER TO {{data.owner}};
+{% endif %}
+{% if 'schema' in data and data.schema != o_data.schema %}
+{% set schema = data.schema%}
+ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    SET SCHEMA {{data.schema}};
+{% endif %}
+{% if 'description' in data and data.description != o_data.description %}
+COMMENT ON TEXT SEARCH DICTIONARY {{conn|qtIdent(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: Dave Page <[email-masked]>
Date: Thu, 14 Apr 2016 20:57:28 +0100

> Please do review it and let me know in case of any issues.

Thanks - committed with a few cosmetic changes, and a couple of minor
issues to resolve:

1) The template is not shown in properties views if it is not in a
system schema (because of the way you are dealing with schema names
etc). Please also check how the existing code will behave, if there is
a template in the public schema with the same name as one in
pg_catalog.

2) Please strip the quotes from the options before displaying them.

I'll create a card for this.

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

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

---

From: Sanket Mehta <[email-masked]>
Date: Fri, 15 Apr 2016 01:29:01 +0530

Thanks for reviewing it Dave.
I will resolve these issues ASAP.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb