Hi,
PFA the revised patch.
Please do review it and let me know if anything is not proper.
Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb
On Thu, May 5, 2016 at 8:19 PM, Harshal Dhumal <
[email protected]> wrote:
> + patch link
>
>
> http://www.postgresql.org/message-id/CAFiP3vwkka+=1foj7kr2zbc4azecoca9eo9dz34-oyy_9ge...@mail.gmail.com
>
> --
> *Harshal Dhumal*
> *Software Engineer *
>
>
>
> EenterpriseDB <http://www.enterprisedb.com>
>
> On Thu, May 5, 2016 at 8:18 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi,
>>
>> PFA first patch for FTS configuration node.
>>
>> It depends upon backgrid select2cell multi select control, for which
>> Harshal has sent the patch recently.
>> Please do apply his patch first and then apply this patch.
>>
>> Please do review it and let me know if any changes are required.
>>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>>
>> --
>> Sent via pgadmin-hackers mailing list ([email protected])
>> To make changes to your subscription:
>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>
>>
>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py
new file mode 100644
index 0000000..fa2d3e6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py
@@ -0,0 +1,937 @@
+##########################################################################
+#
+# 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 Configuration 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
+from pgadmin.browser.server_groups.servers.databases import DatabaseModule
+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 FtsConfigurationModule(SchemaChildModule):
+ """
+ class FtsConfigurationModule(SchemaChildModule)
+
+ A module class for FTS Configuration node derived from SchemaChildModule.
+
+ Methods:
+ -------
+ * __init__(*args, **kwargs)
+ - Method is used to initialize the FtsConfigurationModule 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 Configuration, when any of the schema
+ node is initialized.
+ """
+ NODE_TYPE = 'fts_configuration'
+ COLLECTION_LABEL = _('FTS Configurations')
+
+ def __init__(self, *args, **kwargs):
+ self.min_ver = None
+ self.max_ver = None
+ self.manager = None
+ super(FtsConfigurationModule, 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 DatabaseModule.NODE_TYPE
+
+
+blueprint = FtsConfigurationModule(__name__)
+
+
+class FtsConfigurationView(PGChildNodeView):
+ """
+ class FtsConfigurationView(PGChildNodeView)
+
+ A view class for FTS Configuration node derived from PGChildNodeView.
+ This class is responsible for all the stuff related to view like
+ create/update/delete FTS Configuration,
+ showing properties of node, showing sql in sql pane.
+
+ Methods:
+ -------
+ * __init__(**kwargs)
+ - Method is used to initialize the FtsConfigurationView 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 be used to create all the child node within collection.
+ Here it will create all the FTS Configuration 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, cfgid)
+ - This function will show the properties of the selected FTS Configuration node
+
+ * create(gid, sid, did, scid)
+ - This function will create the new FTS Configuration object
+
+ * update(gid, sid, did, scid, cfgid)
+ - This function will update the data for the selected FTS Configuration node
+
+ * delete(self, gid, sid, did, scid, cfgid):
+ - This function will drop the FTS Configuration object
+
+ * msql(gid, sid, did, scid, cfgid)
+ - This function is used to return modified SQL for the selected node
+
+ * get_sql(data, cfgid)
+ - This function will generate sql from model data
+
+ * sql(gid, sid, did, scid, cfgid):
+ - This function will generate sql to show in sql pane for node.
+
+ * parsers(gid, sid, did, scid):
+ - This function will fetch all ftp parsers from the same schema
+
+ * copyConfig():
+ - This function will fetch all existed fts configurations from same schema
+
+ * tokens():
+ - This function will fetch all tokens from fts parser related to node
+
+ * dictionaries():
+ - This function will fetch all dictionaries related to node
+
+ * dependents(gid, sid, did, scid, cfgid):
+ - This function get the dependents and return ajax response for the node.
+
+ * dependencies(self, gid, sid, did, scid, cfgid):
+ - 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': 'cfgid'}
+ ]
+
+ 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'}],
+ 'parsers': [{'get': 'parsers'},
+ {'get': 'parsers'}],
+ 'copyConfig': [{'get': 'copyConfig'},
+ {'get': 'copyConfig'}],
+ 'tokens': [{'get': 'tokens'}, {'get': 'tokens'}],
+ 'dictionaries': [{}, {'get': 'dictionaries'}],
+ })
+
+ def _init_(self, **kwargs):
+ self.conn = None
+ self.template_path = None
+ self.manager = None
+ super(FtsConfigurationView, self).__init__(**kwargs)
+
+ def module_js(self):
+ """
+ Load JS file (fts_configuration.js) for this module.
+ """
+ return make_response(
+ render_template(
+ "fts_configuration/js/fts_configuration.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_configuration/sql/9.1_plus'
+ return f(*args, **kwargs)
+
+ return wrap
+
+ @check_precondition
+ def list(self, gid, sid, did, scid):
+ """
+ List all FTS Configuration 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)
+
+ return ajax_response(
+ response=res['rows'],
+ status=200
+ )
+
+ @check_precondition
+ def nodes(self, gid, sid, did, scid):
+ """
+ Return all FTS Configurations 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_configuration"
+ ))
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def node(self, gid, sid, did, scid, cfgid):
+ """
+ Return FTS Configuration node to generate node
+
+ Args:
+ gid: Server Group Id
+ sid: Server Id
+ did: Database Id
+ scid: Schema Id
+ cfgid: fts Configuration id
+ """
+
+ sql = render_template(
+ "/".join([self.template_path, 'nodes.sql']),
+ cfgid=cfgid
+ )
+ 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 Configuration node.
+ """))
+
+ for row in rset['rows']:
+ return make_json_response(
+ data=self.blueprint.generate_browser_node(
+ row['oid'],
+ did,
+ row['name'],
+ icon="icon-fts_configuration"
+ ),
+ status=200
+ )
+
+ @check_precondition
+ def properties(self, gid, sid, did, scid, cfgid):
+ """
+ Show properties of FTS Configuration node
+
+ Args:
+ gid: Server Group Id
+ sid: Server Id
+ did: Database Id
+ scid: Schema Id
+ cfgid: fts Configuration id
+ """
+
+ sql = render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ scid=scid,
+ cfgid=cfgid
+ )
+ 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 Configuration node.
+ """))
+
+ # In edit mode fetch token/dictionary list also
+ if cfgid:
+ sql = render_template("/".join([self.template_path,
+ 'tokenDictList.sql']),
+ cfgid=cfgid)
+
+ status, rset = self.conn.execute_dict(sql)
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ res['rows'][0]['tokens'] = rset['rows']
+
+ 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 Configuration object
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ """
+
+ # Mandatory fields to create a new FTS Configuration
+ required_args = [
+ '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
+ )
+ )
+
+ # Either copy config or parser must be present in data
+ if 'copy_config' not in data and 'parser' not in data:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=_(
+ "provide atleast copy config or parser"
+ )
+ )
+
+ 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 cfgid 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, cfgid = self.conn.execute_scalar(sql)
+ if not status:
+ return internal_server_error(errormsg=cfgid)
+
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ cfgid,
+ did,
+ data['name'],
+ icon="icon-fts_configuration"
+ )
+ )
+ 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, cfgid):
+ """
+ This function will update FTS Configuration node
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ :param cfgid: fts Configuration id
+ """
+ data = request.form if request.form else json.loads(
+ request.data.decode())
+
+ # Fetch sql query to update fts Configuration
+ sql = self.get_sql(gid, sid, did, scid, data, cfgid)
+ 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 cfgid is not None:
+ sql = render_template(
+ "/".join([self.template_path, 'nodes.sql']),
+ cfgid=cfgid,
+ 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 Configuration node to update.
+ """))
+
+ data = res['rows'][0]
+ return jsonify(
+ node=self.blueprint.generate_browser_node(
+ cfgid,
+ did,
+ data['name'],
+ icon="icon-fts_configuration"
+ )
+ )
+ # In case FTS Configuration node is not present
+ else:
+ return make_json_response(
+ success=1,
+ info="Nothing to update",
+ data={
+ 'id': cfgid,
+ '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, cfgid):
+ """
+ This function will drop the FTS Configuration object
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ :param cfgid: FTS Configuration 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 Configuration from cfgid
+ sql = render_template(
+ "/".join([self.template_path, 'get_name.sql']),
+ cfgid=cfgid
+ )
+ 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 Configuration node to delete.
+ """))
+
+ # Drop FTS Configuration
+ 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 Configuration dropped"),
+ data={
+ 'id': cfgid,
+ '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, cfgid=None):
+ """
+ This function returns modified SQL
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ :param cfgid: FTS Configuration id
+ """
+ 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, cfgid)
+
+ 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, cfgid=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 cfgid: fts Configuration id
+ """
+ try:
+ # Fetch sql for update
+ if cfgid is not None:
+ sql = render_template(
+ "/".join([self.template_path, 'properties.sql']),
+ cfgid=cfgid,
+ 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 Configuration 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 '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 parsers(self, gid, sid, did, scid):
+ """
+ This function will return fts parsers list for FTS Configuration
+ :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, 'parser.sql']),
+ parser=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 parser control while creating a new FTS Configuration
+ res = [{'label':'', 'value':''}]
+ for row in rset['rows']:
+ if row['schemaoid'] > datlastsysoid:
+ row['prsname'] = row['nspname'] + '.' + row['prsname']
+
+ res.append({'label': row['prsname'],
+ 'value': row['prsname']})
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def copyConfig(self, gid, sid, did, scid):
+ """
+ This function will return copy config list for FTS Configuration
+ :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, 'copy_config.sql']),
+ copy_config=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 copy_config control while creating a new FTS Configuration
+ res = [{'label': '', 'value': ''}]
+ for row in rset['rows']:
+ if row['oid'] > datlastsysoid:
+ row['cfgname'] = row['nspname'] + '.' + row['cfgname']
+
+ res.append({'label': row['cfgname'],
+ 'value': row['cfgname']})
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def tokens(self, gid, sid, did, scid, cfgid=None):
+ """
+ This function will return token list of fts parser node related to
+ current FTS Configuration node
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ :param cfgid: fts configuration id
+ """
+ try:
+ res = []
+ if cfgid is not None:
+ sql = render_template(
+ "/".join([self.template_path, 'parser.sql']),
+ cfgid=cfgid
+ )
+ status, parseroid = self.conn.execute_scalar(sql)
+
+ if not status:
+ return internal_server_error(errormsg=parseroid)
+
+ sql = render_template(
+ "/".join([self.template_path, 'tokens.sql']),
+ parseroid=parseroid
+ )
+ status, rset = self.conn.execute_dict(sql)
+
+ for row in rset['rows']:
+ res.append({'label': row['alias'],
+ 'value': row['alias']})
+
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ except Exception as e:
+ current_app.logger.exception(e)
+ return internal_server_error(errormsg=str(e))
+
+ @check_precondition
+ def dictionaries(self, gid, sid, did, scid, cfgid=None):
+ """
+ This function will return dictionary list for FTS Configuration
+ :param gid: group id
+ :param sid: server id
+ :param did: database id
+ :param scid: schema id
+ """
+ sql = render_template(
+ "/".join([self.template_path,'dictionaries.sql'])
+ )
+ status, rset = self.conn.execute_dict(sql)
+
+ if not status:
+ return internal_server_error(errormsg=rset)
+
+ res = []
+ for row in rset['rows']:
+ res.append({'label': row['dictname'],
+ 'value': row['dictname']})
+ return make_json_response(
+ data=res,
+ status=200
+ )
+
+ @check_precondition
+ def sql(self, gid, sid, did, scid, cfgid):
+ """
+ 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 cfgid: FTS Configuration id
+ """
+ try:
+ sql = render_template(
+ "/".join([self.template_path, 'sql.sql']),
+ cfgid=cfgid,
+ 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 Configuration!\n{0}"
+ ).format(
+ res
+ )
+ )
+
+ if res is None:
+ return gone(
+ _(
+ "ERROR: Couldn't generate reversed engineered query for FTS Configuration 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, cfgid):
+ """
+ This function get the dependents and return ajax response
+ for the FTS Configuration node.
+
+ Args:
+ gid: Server Group ID
+ sid: Server ID
+ did: Database ID
+ scid: Schema ID
+ cfgid: FTS Configuration ID
+ """
+ dependents_result = self.get_dependents(self.conn, cfgid)
+ return ajax_response(
+ response=dependents_result,
+ status=200
+ )
+
+ @check_precondition
+ def dependencies(self, gid, sid, did, scid, cfgid):
+ """
+ This function get the dependencies and return ajax response
+ for the FTS Configuration node.
+
+ Args:
+ gid: Server Group ID
+ sid: Server ID
+ did: Database ID
+ scid: Schema ID
+ cfgid: FTS Configuration ID
+ """
+ dependencies_result = self.get_dependencies(self.conn, cfgid)
+ return ajax_response(
+ response=dependencies_result,
+ status=200
+ )
+
+
+FtsConfigurationView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/coll-fts_configuration.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/coll-fts_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..01bf4ee5bbcc3f7c8598a4fa9d9a76e061bf60e0
GIT binary patch
literal 369
zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAm`Z~Df*BafCZDwc@shmVT^JZv
z^(q?yd7K3vk;OpT1B~5HX4?T7OFdm2LnJOIConW}78-sOmJ~?%c}Bax{<t#d71=#|
zHPqCWN$5@Z@lwFUm#62x_~-Sqk-qU~?)RU_`jzx=&r9~zKjry1W&57Ff8930^nd;L
z<IHbY?b^4AY0<OSr`yBd9c5tswJ<Ah{>^>As$>q&(poX=^>V-YH|>j=m=Dhq*)Xe{
zJ^#4MHC^^d=CI8letex_H{)<aR+wI0^MmFqG5h<@u)mICPGw@SSyf=o@G(9S=wQ_n
z*NBpo#FA92<f2p{#b9J$XrOCoq-$UpVq{=tVr*q%qHSPmWnf@2Q&kQ{LvDUbW?Cgg
ggMlSj14y-%ff+=@sp+9>fEpM)UHx3vIVCg!0N_P|FaQ7m
literal 0
HcmV?d00001
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/fts_configuration.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/fts_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a5caf5373670cc684605d42c9eb21724fd75523
GIT binary patch
literal 346
zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAm`Z~Df*BafCZDwc@shmVT^JZv
z^(q?yd7K3vk;OpT1B~5HX4?T7eV#6kArhC96ArLWyRPcz_RN1y<+t?5ekwm_`v3pt
zpXShUW$y3)=KnrDaoe%($o&2Lz8?Sn->2o%@1(l@)iv**{hVA{@b9+t^71?jt76M3
z|6d>7dilAY%nVDxQ~!THIdrkwu(MR)r>SB6yt#Ml)tB+7IbQubmD}%*%Lhj$?|Vug
zC9j3V?(aLp-Wjqs@c=Kwu}j{!7{ZG80bQnA;u=wsl30>zm0Xkxq!^403=MP*jdTqR
zLyQcpOpL8eOtcM5tqcq-W~$1eXvob^$xN$+XfUt@YXGUXGBAT^I5j<V4NwDvr>mdK
II;Vst0AK%q4gdfE
literal 0
HcmV?d00001
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js
new file mode 100644
index 0000000..782d664
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js
@@ -0,0 +1,585 @@
+define(
+ ['jquery', 'underscore', 'underscore.string', 'pgadmin',
+ 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+
+// Model for tokens control
+ var TokenModel = pgAdmin.Browser.Node.Model.extend({
+ defaults: {
+ token: undefined,
+ dictname: undefined
+ },
+ // Define the schema for the token/dictionary list
+ schema: [
+ {
+ id: 'token', label:'Token', type:'text', group: null,
+ cellHeaderClasses:'width_percent_50', editable: true,
+ editable: false, cell: 'string', url: 'tokens'
+ },{
+ id: 'dictname', label: 'Dictionaries', type: 'text', group:null,
+ cellHeaderClasses:'width_percent_50', editable: true,
+ cell:Backgrid.Extension.MultiSelectAjaxCell, url: 'dictionaries'
+ }
+ ],
+ // Validation for token and dictionary list
+ validate: function() {
+ // Clear any existing errors.
+ var msg;
+ this.errorModel.clear();
+ var token = this.get('token');
+ var dictionary = this.get('dictname');
+
+ if (_.isNull(token) ||
+ _.isUndefined(token) ||
+ String(token).replace(/^\s+|\s+$/g, '') == '') {
+ msg = '{{ _('Token can not be empty!') }}';
+ this.errorModel.set('token',msg);
+ return msg;
+ }
+
+ if (_.isNull(dictionary) ||
+ _.isUndefined(dictionary) ||
+ String(dictionary).replace(/^\s+|\s+$/g, '') == '') {
+ msg = '{{ _('Dictionary name can not be empty!') }}';
+ this.errorModel.set('dictname',msg);
+ return msg;
+ }
+ return null;
+ }
+ });
+
+// Customized control for token control
+ var TokenControl = Backform.TokenControl =
+ Backform.UniqueColCollectionControl.extend({
+
+ initialize: function(opts) {
+ Backform.UniqueColCollectionControl.prototype.initialize.apply(
+ this, arguments
+ );
+
+ var self = that = this,
+ node = 'fts_configuration',
+ headerSchema = [{
+ id: 'token', label:'', type:'text', url: 'tokens',
+ node:'fts_configuration', canAdd: true, 'url_with_id': true,
+
+ // Defining control for tokens dropdown control in header
+ control: Backform.NodeAjaxOptionsControl.extend({
+ formatter: Backform.NodeAjaxOptionsControl.prototype.formatter,
+ initialize: function() {
+ Backform.NodeAjaxOptionsControl.prototype.initialize.apply(
+ this,
+ arguments
+ );
+ var self = this,
+ url = self.field.get('url') || self.defaults.url,
+ m = self.model.top || self.model;
+
+ /* Fetch the tokens/dict list from 'that' node.
+ * Here 'that' refers to unique collection control where
+ * 'self' refers to nodeAjaxOptions control for dictionary
+ */
+ var cfgid = that.model.get('oid');
+ if (url) {
+ var node = this.field.get('schema_node'),
+ node_info = this.field.get('node_info'),
+ full_url = node.generate_url.apply(
+ node, [
+ null, url, this.field.get('node_data'),
+ this.field.get('url_with_id') || false,
+ node_info
+ ]),
+ cache_level = this.field.get('cache_level') || node.type,
+ cache_node = this.field.get('cache_node');
+
+ cache_node = (cache_node &&
+ pgAdmin.Browser.Nodes['cache_node'])
+ || node;
+
+ /*
+ * We needs to check, if we have already cached data
+ * for this url. If yes - use it, and do not bother about
+ * fetching it again.
+ */
+ var data = cache_node.cache(url, node_info, cache_level);
+
+ // Fetch token/dictionary list
+ if (this.field.get('version_compatible') &&
+ (_.isUndefined(data) || _.isNull(data))) {
+ m.trigger('pgadmin:view:fetching', m, self.field);
+ $.ajax({
+ async: false,
+ url: full_url,
+ success: function(res) {
+ /*
+ * We will cache this data for short period of time for
+ * avoiding same calls.
+ */
+ data = cache_node.cache(url,
+ node_info,
+ cache_level,
+ res.data
+ );
+ },
+ error: function() {
+ m.trigger('pgadmin:view:fetch:error', m, self.field);
+ }
+ });
+ m.trigger('pgadmin:view:fetched', m, self.field);
+ }
+
+ // It is feasible that the data may not have been fetched.
+ data = (data && data.data) || [];
+
+ /*
+ * Transform the data
+ */
+ transform = (this.field.get('transform')
+ || self.defaults.transform);
+ if (transform && _.isFunction(transform)) {
+ self.field.set('options', transform.bind(self, data));
+ } else {
+ self.field.set('options', data);
+ }
+ }
+ }
+ }),
+ // Select2 control for adding new tokens
+ select2: {
+ allowClear: true, width: 'style',
+ placeholder: 'Select token'
+ },
+ first_empty: true,
+ disabled: function(m) {
+ return _.isUndefined(self.model.get('oid'));
+ }
+ }],
+ headerDefaults = {token: null},
+ // Grid columns backgrid
+ gridCols = ['token', 'dictname'];
+
+ // Creating model for header control which is used to add new tokens
+ self.headerData = new (Backbone.Model.extend({
+ defaults: headerDefaults,
+ schema: headerSchema
+ }))({});
+
+ // Creating view from header schema in tokens control
+ var headerGroups = Backform.generateViewSchema(
+ self.field.get('node_info'), self.headerData, 'create',
+ self.field.get('schema_node'), self.field.get('node_data')
+ ),
+ fields = [];
+
+ _.each(headerGroups, function(o) {
+ fields = fields.concat(o.fields);
+ });
+ self.headerFields = new Backform.Fields(fields);
+
+ // creating grid using grid columns
+ self.gridSchema = Backform.generateGridColumnsFromModel(
+ self.field.get('node_info'), self.field.get('model'),
+ 'edit', gridCols, self.field.get('schema_node')
+ );
+
+ // Providing behaviour control functions to header and grid control
+ self.controls = [];
+ self.listenTo(self.headerData, "change", self.headerDataChanged);
+ self.listenTo(self.headerData, "select2", self.headerDataChanged);
+ self.listenTo(self.collection, "add", self.onAddorRemoveTokens);
+ self.listenTo(self.collection, "remove", self.onAddorRemoveTokens);
+ },
+
+ // Template for creating header view
+ generateHeader: function(data) {
+ var header = [
+ '<div class="subnode-header-form">',
+ ' <div class="container-fluid">',
+ ' <div class="row">',
+ ' <div class="col-xs-4">',
+ ' <label class="control-label"><%-token_label%></label>',
+ ' </div>',
+ ' <div class="col-xs-4" header="token"></div>',
+ ' <div class="col-xs-4">',
+ ' <button class="btn-sm btn-default add" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><%-add_label%></buttton>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>',].join("\n")
+
+ _.extend(data, {
+ token_label: '{{ _('Tokens')}}',
+ add_label: '{{ _('ADD')}}'
+ });
+
+ var self = this,
+ headerTmpl = _.template(header),
+ $header = $(headerTmpl(data)),
+ controls = this.controls;
+
+ self.headerFields.each(function(field) {
+ var control = new (field.get("control"))({
+ field: field,
+ model: self.headerData
+ });
+
+ $header.find('div[header="' + field.get('name') + '"]').append(
+ control.render().$el
+ );
+
+ control.$el.find('.control-label').remove();
+ controls.push(control);
+ });
+
+ // We should not show add button in properties mode
+ if (data.mode == 'properties') {
+ $header.find("button.add").remove();
+ }
+ self.$header = $header;
+ return $header;
+ },
+
+ // Providing event handler for add button in header
+ events: _.extend(
+ {}, Backform.UniqueColCollectionControl.prototype.events,
+ {'click button.add': 'addTokens'}
+ ),
+
+ // Show token/dictionary grid
+ showGridControl: function(data) {
+
+ var self = this,
+ titleTmpl = _.template("<div class='subnode-header'></div>"),
+ $gridBody = $("<div></div>", {
+ class:'pgadmin-control-group backgrid form-group col-xs-12 object subnode'
+ }).append(
+ titleTmpl({label: data.label})
+ );
+
+ $gridBody.append(self.generateHeader(data));
+
+ var gridColumns = _.clone(this.gridSchema.columns);
+
+ // Insert Delete Cell into Grid
+ if (data.disabled == false && data.canDelete) {
+ gridColumns.unshift({
+ name: "pg-backform-delete", label: "",
+ cell: Backgrid.Extension.DeleteCell,
+ editable: false, cell_priority: -1
+ });
+ }
+
+ if (self.grid) {
+ self.grid.remove();
+ self.grid.null;
+ }
+ // Initialize a new Grid instance
+ var grid = self.grid = new Backgrid.Grid({
+ columns: gridColumns,
+ collection: self.collection,
+ className: "backgrid table-bordered"
+ });
+ self.$grid = grid.render().$el;
+
+ $gridBody.append(self.$grid);
+
+ // Find selected dictionaries in grid and show it all together
+ setTimeout(function() {
+ self.headerData.set({
+ 'token': self.$header.find(
+ 'div[header="token"] select'
+ ).val()
+ }, {silent:true}
+ );
+ }, 10);
+
+ // Render node grid
+ return $gridBody;
+ },
+
+ // When user change the header control to add a new token
+ headerDataChanged: function() {
+ var self = this, val,
+ data = this.headerData.toJSON(),
+ inSelected = false,
+ checkVars = ['token'];
+
+ if (!self.$header) {
+ return;
+ }
+
+ if (self.control_data.canAdd) {
+ self.collection.each(function(m) {
+ if (!inSelected) {
+ _.each(checkVars, function(v) {
+ if (!inSelected) {
+ val = m.get(v);
+ inSelected = ((
+ (_.isUndefined(val) || _.isNull(val)) &&
+ (_.isUndefined(data[v]) || _.isNull(data[v]))
+ ) ||
+ (val == data[v]));
+ }
+ });
+ }
+ });
+ }
+ else {
+ inSelected = true;
+ }
+
+ self.$header.find('button.add').prop('disabled', inSelected);
+ },
+
+ // Get called when user click on add button header
+ addTokens: function(ev) {
+ ev.preventDefault();
+ var self = this,
+ token = self.headerData.get('token');
+
+ if (!token || token == '') {
+ return false;
+ }
+
+ var coll = self.model.get(self.field.get('name')),
+ m = new (self.field.get('model'))(
+ self.headerData.toJSON(), {
+ silent: true, top: self.model.top,
+ collection: coll, handler: coll
+ });
+
+ // Add new row in grid
+ coll.add(m);
+
+ // Index of newly created row in grid
+ var idx = coll.indexOf(m);
+
+ // idx may not be always > -1 because our UniqueColCollection may
+ // remove 'm' if duplicate value found.
+ if (idx > -1) {
+ self.$grid.find('.new').removeClass('new');
+ var newRow = self.grid.body.rows[idx].$el;
+ newRow.addClass("new");
+ $(newRow).pgMakeVisible('backform-tab');
+ } else {
+ delete m;
+ }
+
+ return false;
+ },
+
+ // When user delete token/dictionary entry from grid
+ onAddorRemoveTokens: function() {
+ var self = this;
+
+ /*
+ * Wait for collection to be updated before checking for the button to
+ * be enabled, or not.
+ */
+ setTimeout(function() {
+ self.collection.trigger('pgadmin:tokens:updated', self.collection);
+ self.headerDataChanged();
+ }, 10);
+ },
+
+ // When control is about to destroy
+ remove: function() {
+ /*
+ * Stop listening the events registered by this control.
+ */
+ this.stopListening(this.headerData, "change", this.headerDataChanged);
+ this.listenTo(this.headerData, "select2", this.headerDataChanged);
+ this.listenTo(this.collection, "remove", this.onAddorRemoveTokens);
+
+ TokenControl.__super__.remove.apply(this, arguments);
+
+ // Remove the header model
+ delete (this.headerData);
+
+ }
+ });
+
+
+ // Extend the collection class for FTS Configuration
+ if (!pgBrowser.Nodes['coll-fts_configuration']) {
+ var fts_dictionaries = pgAdmin.Browser.Nodes['coll-fts_configuration'] =
+ pgAdmin.Browser.Collection.extend({
+ node: 'fts_configuration',
+ label: '{{ _('FTS Configurations') }}',
+ type: 'coll-fts_configuration',
+ columns: ['name', 'description']
+ });
+ };
+
+ // Extend the node class for FTS Configuration
+ if (!pgBrowser.Nodes['fts_configuration']) {
+ pgAdmin.Browser.Nodes['fts_configuration'] = pgAdmin.Browser.Node.extend({
+ parent_type: ['schema', 'catalog'],
+ type: 'fts_configuration',
+ sqlAlterHelp: 'sql-altertsconfiguration.html',
+ sqlCreateHelp: 'sql-createtsconfiguration.html',
+ 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 Configuration
+ pgBrowser.add_menus([{
+ name: 'create_fts_configuration_on_schema', node: 'schema',
+ module: this, category: 'create', priority: 4,
+ applies: ['object', 'context'], callback: 'show_obj_properties',
+ label: '{{_('FTS Configuration...')}}',
+ icon: 'wcTabIcon icon-fts_configuration', data: {action: 'create'}
+ },{
+ name: 'create_fts_configuration_on_coll', module: this, priority: 4,
+ node: 'coll-fts_configuration', applies: ['object', 'context'],
+ callback: 'show_obj_properties', category: 'create',
+ label: '{{ _('FTS Configuration...') }}', data: {action: 'create'},
+ icon: 'wcTabIcon icon-fts_configuration'
+ },{
+ name: 'create_fts_configuration', node: 'fts_configuration',
+ module: this, applies: ['object', 'context'],
+ callback: 'show_obj_properties', category: 'create', priority: 4,
+ label: '{{_('FTS Configuration...')}}', data: {action: 'create'},
+ icon: 'wcTabIcon icon-fts_configuration'
+ }]);
+ },
+
+ // Defining model for FTS Configuration node
+ model: pgAdmin.Browser.Node.Model.extend({
+ defaults: {
+ name: undefined, // FTS Configuration name
+ owner: undefined, // FTS Configuration owner
+ description: undefined, // Comment on FTS Configuration
+ schema: undefined, // Schema name FTS Configuration belongs to
+ prsname: undefined, // FTS parser list for FTS Configuration node
+ copy_config: undefined, // FTS configuration list to copy from
+ tokens: undefined // token/dictionary pair list for node
+ },
+ 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 Configuration
+ 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: 'prsname', label: '{{ _('Parser')}}',type: 'text',
+ url: 'parsers', first_empty: true,
+ group: '{{ _('Definition') }}', control: 'node-ajax-options',
+ deps: ['copy_config'],
+ //disable parser when user select copy_config manually and vica-versa
+ disabled: function(m) {
+ var copy_config = m.get('copy_config');
+ return m.isNew() &&
+ (_.isNull(copy_config) ||
+ _.isUndefined(copy_config) ||
+ copy_config === '') ? false : true;
+ }
+ },{
+ id: 'copy_config', label: '{{ _('Copy Config')}}',type: 'text',
+ mode: ['create'], group: '{{ _('Definition') }}',
+ control: 'node-ajax-options', url: 'copyConfig', deps: ['prsname'],
+
+ //disable copy_config when user select parser manually and vica-versa
+ disabled: function(m) {
+ var parser = m.get('prsname');
+ return m.isNew() &&
+ (_.isNull(parser) ||
+ _.isUndefined(parser) ||
+ parser === '') ? false : true;
+ }
+ },{
+ id: 'tokens', label: '{{ _('Tokens') }}', type: 'collection',
+ group: '{{ _('Tokens') }}', control: TokenControl,
+ model: TokenModel, columns: ['token', 'dictionary'],
+ uniqueCol : ['token'], mode: ['create','edit'],
+ canAdd: true, canEdit: false, canDelete: true
+ }],
+
+ /*
+ * Triggers control specific error messages for name,
+ * copy_config/parser and schema, if any one of them is not specified
+ * while creating new fts configuration
+ */
+ validate: function(keys){
+ var msg;
+ var name = this.get('name');
+ var parser = this.get('prsname');
+ var copy_config_or_parser = !(parser === '' ||
+ _.isUndefined(parser) ||
+ _.isNull(parser)) ?
+ this.get('prsname') : this.get('copy_config');
+ var schema = this.get('schema');
+
+ // Clear the existing error model
+ this.errorModel.clear();
+ this.trigger('on-status-clear');
+
+ // Validate the name
+ if (_.isUndefined(name) ||
+ _.isNull(name) ||
+ String(name).replace(/^\s+|\s+$/g, '') == '') {
+ msg = '{{ _('Name must be specified!') }}';
+ this.errorModel.set('name', msg);
+ return msg;
+ }
+
+ // Validate parser or copy_config
+ else if (_.isUndefined(copy_config_or_parser) ||
+ _.isNull(copy_config_or_parser) ||
+ String(copy_config_or_parser).replace(/^\s+|\s+$/g, '') == '') {
+ msg = '{{ _('Select parser or configuration to copy!') }}';
+ this.errorModel.set('parser', msg);
+ return msg;
+ }
+
+ // Validate schema
+ else if (_.isUndefined(schema) ||
+ _.isNull(schema) ||
+ String(schema).replace(/^\s+|\s+$/g, '') == '') {
+ msg = '{{ _('Schema must be selected!') }}';
+ this.errorModel.set('schema', msg);
+ return msg;
+ }
+
+ return null;
+ }
+ })
+ });
+ }
+
+return pgBrowser.Nodes['coll-fts_configuration'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql
new file mode 100644
index 0000000..584fc00
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql
@@ -0,0 +1,15 @@
+{# FETCH copy config for FTS CONFIGURATION #}
+{% if copy_config %}
+SELECT
+ cfg.oid,
+ cfgname,
+ nspname,
+ n.oid as schemaoid
+FROM
+ pg_ts_config cfg
+ JOIN pg_namespace n
+ ON n.oid=cfgnamespace
+ORDER BY
+ nspname,
+ cfgname
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql
new file mode 100644
index 0000000..26d79f3
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql
@@ -0,0 +1,15 @@
+{# CREATE FTS CONFIGURATION Statement #}
+{% if data and data.schema and data.name %}
+CREATE TEXT SEARCH CONFIGURATION {{ conn|qtIdent(data.schema, data.name) }} (
+{% if 'copy_config' in data %}
+ COPY={{ data.copy_config }}
+{% elif 'prsname' in data %}
+ PARSER = {{ data.prsname }}
+{% endif %}
+);
+{# Description for FTS_CONFIGURATION #}
+
+{% if data.description %}
+COMMENT ON TEXT SEARCH CONFIGURATION {{ 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_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql
new file mode 100644
index 0000000..0052f61
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql
@@ -0,0 +1,4 @@
+{# DROP FTS CONFIGURATION Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH CONFIGURATION {{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_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql
new file mode 100644
index 0000000..f0e7b0f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql
@@ -0,0 +1,7 @@
+{# FETCH DICTIONARIES statement #}
+SELECT
+ dictname
+FROM
+ pg_ts_dict
+ORDER BY
+ dictname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql
new file mode 100644
index 0000000..7238b8c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql
@@ -0,0 +1,17 @@
+{# GET FTS CONFIGURATION name #}
+{% if cfgid %}
+SELECT
+ cfg.cfgname as name,
+ (
+ SELECT
+ nspname
+ FROM
+ pg_namespace
+ WHERE
+ oid = cfg.cfgnamespace
+ ) as schema
+FROM
+ pg_ts_config cfg
+WHERE
+ cfg.oid = {{cfgid}}::OID;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql
new file mode 100644
index 0000000..ec50acb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql
@@ -0,0 +1,13 @@
+{# FETCH FTS CONFIGURATION NAME statement #}
+SELECT
+ oid, cfgname as name
+FROM
+ pg_ts_config cfg
+WHERE
+{% if scid %}
+ cfg.cfgnamespace = {{scid}}::OID
+{% elif cfgid %}
+ cfg.oid = {{cfgid}}::OID
+{% endif %}
+
+ORDER BY name
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql
new file mode 100644
index 0000000..cbca9c9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql
@@ -0,0 +1,24 @@
+{# PARSER name from FTS CONFIGURATION OID #}
+{% if cfgid %}
+SELECT
+ cfgparser
+FROM
+ pg_ts_config
+where
+ oid = {{cfgid}}::OID
+{% endif %}
+
+
+{# PARSER list #}
+{% if parser %}
+SELECT
+ prsname,
+ nspname,
+ n.oid as schemaoid
+FROM
+ pg_ts_parser
+ JOIN pg_namespace n
+ ON n.oid=prsnamespace
+ORDER BY
+ prsname;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql
new file mode 100644
index 0000000..a2f376e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql
@@ -0,0 +1,25 @@
+{# FETCH properties for FTS CONFIGURATION #}
+SELECT
+ cfg.oid,
+ cfg.cfgname as name,
+ pg_get_userbyid(cfg.cfgowner) as owner,
+ cfg.cfgparser as parser,
+ cfg.cfgnamespace as schema,
+ parser.prsname as prsname,
+ description
+FROM
+ pg_ts_config cfg
+ LEFT OUTER JOIN pg_ts_parser parser
+ ON parser.oid=cfg.cfgparser
+ LEFT OUTER JOIN pg_description des
+ ON (des.objoid=cfg.oid AND des.classoid='pg_ts_config'::regclass)
+WHERE
+{% if scid %}
+ cfg.cfgnamespace = {{scid}}::OID
+{% elif name %}
+ cfg.cfgname = {{name|qtLiteral}}
+{% endif %}
+{% if cfgid %}
+ AND cfg.oid = {{cfgid}}::OID
+{% endif %}
+ORDER BY cfg.cfgname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/schema.sql
new file mode 100644
index 0000000..b56ceb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/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_config cfg
+ ON cfg.cfgnamespace = nsp.oid
+WHERE
+ cfg.oid = {{data.id}}::OID
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql
new file mode 100644
index 0000000..e31307d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql
@@ -0,0 +1,75 @@
+{# REVERSED ENGINEERED SQL FOR FTS CONFIGURATION #}
+{% if cfgid and scid %}
+SELECT
+ array_to_string(array_agg(sql), E'\n\n') as sql
+FROM
+ (
+ SELECT
+ E'-- Text Search CONFIGURATION: ' || quote_ident(nspname) || E'.'
+ || quote_ident(cfg.cfgname) ||
+ E'\n\n-- DROP TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+ E'.' || quote_ident(cfg.cfgname) ||
+ E'\n\nCREATE TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+ E'.' || quote_ident(cfg.cfgname) || E' (\n' ||
+ E'\tPARSER = ' || parsername ||
+ E'\n);' ||
+ CASE
+ WHEN description IS NOT NULL THEN
+ E'\n\nCOMMENT ON TEXT SEARCH CONFIGURATION ' ||
+ quote_ident(nspname) || E'.' || quote_ident(cfg.cfgname) ||
+ E' IS ' || pg_catalog.quote_literal(description) || E';'
+ ELSE ''
+ END ||
+ (
+ SELECT array(
+ SELECT
+ 'ALTER TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+ E'.' || quote_ident(cfg.cfgname) || ' ADD MAPPING FOR ' ||
+ t.alias || ' WITH ' ||
+ array_to_string(array_agg(dict.dictname), ', ') || ';'
+ FROM
+ pg_ts_config_map map
+ LEFT JOIN (
+ SELECT
+ tokid,
+ alias
+ FROM
+ pg_catalog.ts_token_type(cfg.cfgparser)
+ ) t ON (t.tokid = map.maptokentype)
+ LEFT OUTER JOIN pg_ts_dict dict ON (map.mapdict = dict.oid)
+ WHERE
+ map.mapcfg = cfg.oid
+ GROUP BY t.alias
+ ORDER BY t.alias)
+ ) as sql
+ FROM
+ pg_ts_config cfg
+ LEFT JOIN (
+ SELECT
+ des.description as description,
+ des.objoid as descoid
+ FROM
+ pg_description des
+ WHERE
+ des.objoid={{cfgid}}::OID AND des.classoid='pg_ts_config'::regclass
+ ) a ON (a.descoid = cfg.oid)
+ LEFT JOIN (
+ SELECT
+ nspname,
+ nsp.oid as noid
+ FROM
+ pg_namespace nsp
+ WHERE
+ oid = {{scid}}::OID
+ ) b ON (b.noid = cfg.cfgnamespace)
+ LEFT JOIN(
+ SELECT
+ prs.prsname as parsername,
+ prs.oid as oid
+ FROM
+ pg_ts_parser prs
+ )c ON (c.oid = cfg.cfgparser)
+ WHERE
+ cfg.oid={{cfgid}}::OID
+ ) e;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql
new file mode 100644
index 0000000..e8a2d5b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql
@@ -0,0 +1,23 @@
+{# Fetch token/dictionary list for FTS CONFIGURATION #}
+{% if cfgid %}
+SELECT
+ (
+ SELECT
+ t.alias
+ FROM
+ pg_catalog.ts_token_type(cfgparser) AS t
+ WHERE
+ t.tokid = maptokentype
+ ) AS token,
+ array_agg(dictname) AS dictname
+FROM
+ pg_ts_config_map
+ LEFT OUTER JOIN pg_ts_config ON mapcfg = pg_ts_config.oid
+ LEFT OUTER JOIN pg_ts_dict ON mapdict = pg_ts_dict.oid
+WHERE
+ mapcfg={{cfgid}}::OID
+GROUP BY
+ token
+ORDER BY
+ 1
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql
new file mode 100644
index 0000000..ce0c5eb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql
@@ -0,0 +1,10 @@
+{# Tokens for FTS CONFIGURATION node #}
+
+{% if parseroid %}
+SELECT
+ alias
+FROM
+ ts_token_type({{parseroid}}::OID)
+ORDER BY
+ alias
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql
new file mode 100644
index 0000000..e497bee
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql
@@ -0,0 +1,51 @@
+{# UPDATE statement for FTS CONFIGURATION #}
+{% 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 CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}}
+ RENAME TO {{data.name}};
+
+{% endif %}
+{% if 'tokens' in data %}
+{% if'changed' in data.tokens %}
+{% for tok in data.tokens.changed %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+ ALTER MAPPING FOR {{tok.token}}
+ WITH {% for dict in tok.dictname %}{{dict}}{% if not loop.last %}, {% endif %}{% endfor %};
+
+{% endfor %}
+{% endif %}
+{% if'added' in data.tokens %}
+{% for tok in data.tokens.added %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+ ADD MAPPING FOR {{tok.token}}
+ WITH {% for dict in tok.dictname %}{{dict}}{% if not loop.last %}, {% endif %}{% endfor %};
+
+{% endfor %}
+{% endif %}
+{% if'deleted' in data.tokens %}
+{% for tok in data.tokens.deleted %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+ DROP MAPPING FOR {{tok.token}};
+
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'owner' in data and data.owner != o_data.owner %}
+ALTER TEXT SEARCH CONFIGURATION {{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 CONFIGURATION {{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 CONFIGURATION {{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 protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers