Hi, PFA the revised patch. Response is lnline.
Regards, Sanket Mehta Sr Software engineer Enterprisedb On Tue, May 17, 2016 at 12:54 PM, Harshal Dhumal < harshal.dhu...@enterprisedb.com> wrote: > Hi Sanket, > > Please find my review comments below: > > 1. In create mode it generates wrong sql. > > CREATE TEXT SEARCH CONFIGURATION test.asdf ( > COPY= > ); > > steps to reproduce: > a] Fill up any necessary fields in general tab. > b] On definition tab set Copy config filed and navigate to SQL tab. > c] Come back again to definition tab clear Copy config field and set > Parser field and again navigate to SQL tab > Fixed > > 2. Create new FTS configuration with parser throws below error. > > provide atleast copy config or parser. > > (this is because you have used key name for parser in js as "prsname" and > in python you have applied validations on key "parser") > Fixed > > 3. When we clear dictionary for any token using cross (x) button in > select2; JavaScript code for selecct2 fails. (see attached screenshot) > This occurs due to issue in select2 control. Its a generalized issue which will be resolved later. > > > > > > > -- > *Harshal Dhumal* > *Software Engineer * > > > > EenterpriseDB <http://www.enterprisedb.com> > > On Mon, May 16, 2016 at 7:37 PM, Sanket Mehta < > sanket.me...@enterprisedb.com> wrote: > >> Hi, >> >> Revised patch is attached with this mail. >> My response is inline. >> >> >> Regards, >> Sanket Mehta >> Sr Software engineer >> Enterprisedb >> >> On Fri, May 13, 2016 at 6:20 PM, Akshay Joshi < >> akshay.jo...@enterprisedb.com> wrote: >> >>> Hi Sanket >>> >>> Below are my review comments: >>> >>> >>> - Add button should be disabled in Tokens tab while creating. >>> >>> Done >> >>> >>> - Unable to click on down arrow for token select control. >>> >>> Done >> >>> >>> - Title should be change from "Create FTS-dictionaries" to "Create >>> FTS-Configuration". >>> >>> Done >> >>> >>> - Tree node is not getting refreshed on name change. >>> >>> Done >> >>> >>> - Unable to rename FTS Configuration when name contains any capital >>> letter. >>> >>> Done >> >>> >>> - If user tries to add already existing token then respective row in >>> the backgrid should be highlighted. >>> >>> Done >> >>> >>> - "URL not found" when click on SQL help icon from the properties >>> dialog. >>> >>> Done >> >>> >>> - SQL should not be generated when remove and add the same token >>> again. Currently it is creating two sql's one for remove and another >>> is for add. >>> - *Fix for the above issue is*: - Add "keys: ['token']," to >>> TokenModel in your js file. >>> >>> Done >> >> >>> >>> On Fri, May 13, 2016 at 2:58 PM, Sanket Mehta < >>> sanket.me...@enterprisedb.com> wrote: >>> >>>> Hi All, >>>> >>>> Last patch for FTS configuration does not contain node.ui.js file >>>> Kindly ignore it. >>>> >>>> Here is the new revised patch attached with this mail. >>>> Please do review it and let me know if any changes required >>>> >>>> Regards, >>>> Sanket Mehta >>>> Sr Software engineer >>>> Enterprisedb >>>> >>>> On Thu, May 12, 2016 at 4:38 PM, Sanket Mehta < >>>> sanket.me...@enterprisedb.com> wrote: >>>> >>>>> 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 < >>>>> harshal.dhu...@enterprisedb.com> 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 < >>>>>> sanket.me...@enterprisedb.com> 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 ( >>>>>>> pgadmin-hackers@postgresql.org) >>>>>>> To make changes to your subscription: >>>>>>> http://www.postgresql.org/mailpref/pgadmin-hackers >>>>>>> >>>>>>> >>>>>> >>>>> >>>> >>>> >>>> -- >>>> Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) >>>> To make changes to your subscription: >>>> http://www.postgresql.org/mailpref/pgadmin-hackers >>>> >>>> >>> >>> >>> -- >>> *Akshay Joshi* >>> *Principal Software Engineer * >>> >>> >>> >>> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246* >>> >> >> >> >> -- >> Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) >> 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..6b87740 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py @@ -0,0 +1,951 @@ +########################################################################## +# +# 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 +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. + + * csssnippets() + - Load style sheets for this module + """ + 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 schemas.SchemaModule.NODE_TYPE + + @property + def csssnippets(self): + snippets = [render_template( + 'fts_configuration/css/fts_configuration.css', + node_type = self.node_type, + _=_ + )] + snippets.extend(super(SchemaChildModule,self).csssnippets) + return snippets + +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 'prsname' 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 make_json_response( + success=1, + info="FTS Configuration Updated.", + data={ + 'id': cfgid, + 'sid': sid, + 'gid': gid, + 'did': did, + 'scid': scid + } + ) + # 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/css/fts_configuration.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/css/fts_configuration.css new file mode 100644 index 0000000..b8ef393 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/css/fts_configuration.css @@ -0,0 +1,3 @@ +.pgadmin-controls { + min-width: 0px !important; +} \ No newline at end of file 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..b1d73e6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js @@ -0,0 +1,579 @@ +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 + }, + keys: ['token'], + // 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(); + } + + // Disable add button in token control in create mode + if(data.mode == 'create') { + $header.find("button.add").attr('disabled', true); + } + + 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 = (_.isEmpty(data) || _.isUndefined(data)), + checkVars = ['token']; + + if (!self.$header) { + return; + } + + 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 + }), + checkVars = ['token'], + idx = -1; + + // Find if token exists in grid + self.collection.each(function(m) { + _.each(checkVars, function(v) { + val = m.get(v); + if(val == token) { + idx = coll.indexOf(m); + } + }); + }); + + + + // remove 'm' if duplicate value found. + if (idx == -1) { + coll.add(m); + idx = coll.indexOf(m); + } + self.$grid.find('.new').removeClass('new'); + var newRow = self.grid.body.rows[idx].$el; + newRow.addClass("new"); + //$(newRow).pgMakeVisible('table-bordered'); + $(newRow).pgMakeVisible('backform-tab'); + + + 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_configurations = 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-altertsconfig.html', + sqlCreateHelp: 'sql-createtsconfig.html', + canDrop: true, + canDropCascade: true, + label: '{{ _('FTS Configuration') }}', + 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..88f428b --- /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 and data.copy_config != '' %} + COPY={{ data.copy_config }} +{% elif 'prsname' in data and data.prsname != '' %} + 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..77599f8 --- /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 {{conn|qtIdent(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 diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js index 8bf71dc..7e84935 100644 --- a/web/pgadmin/browser/static/js/node.ui.js +++ b/web/pgadmin/browser/static/js/node.ui.js @@ -504,6 +504,35 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) { }) }); + // Extend the browser's node model class to create a option/value pair + var MultiSelectAjaxCell = Backgrid.Extension.MultiSelectAjaxCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({ + defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, { + transform: undefined, + url_with_id: false, + select2: { + allowClear: true, + placeholder: 'Select from the list', + width: 'style', + multiple: true + }, + opt: { + label: null, + value: null, + image: null, + selected: false + } + }), + getValueFromDOM: function() { + var res = []; + + this.$el.find("select").find(':selected').each(function() { + res.push($(this).attr('value')); + }); + + return res; + }, + }); + /* * Control to select multiple columns. */
-- Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers