From 7305986e14d9e895886ecb6e803b34b05dd106b0 Mon Sep 17 00:00:00 2001 From: Sanket Mehta Date: Thu, 14 Apr 2016 20:53:14 +0100 Subject: [PATCH] FTS Dictionary support. --- .../schemas/fts_dictionaries/__init__.py | 818 ++++++++++++++++++ .../fts_dictionary/js/fts_dictionary.js | 185 ++++ .../fts_dictionary/sql/9.1_plus/create.sql | 13 + .../fts_dictionary/sql/9.1_plus/delete.sql | 23 + .../fts_dictionary/sql/9.1_plus/nodes.sql | 13 + .../sql/9.1_plus/properties.sql | 24 + .../fts_dictionary/sql/9.1_plus/schema.sql | 19 + .../fts_dictionary/sql/9.1_plus/sql.sql | 52 ++ .../fts_dictionary/sql/9.1_plus/templates.sql | 11 + .../fts_dictionary/sql/9.1_plus/update.sql | 49 ++ 10 files changed, 1207 insertions(+) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py new file mode 100644 index 000000000..fcbecbbf4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/__init__.py @@ -0,0 +1,818 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Defines views for management of Fts Dictionary node""" + +import json +from flask import render_template, make_response, current_app, request, jsonify +from flask.ext.babel import gettext as _ +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error, gone +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.server_groups.servers.databases.schemas.utils \ + import SchemaChildModule +import pgadmin.browser.server_groups.servers.databases.schemas as schemas +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps + + +class FtsDictionaryModule(SchemaChildModule): + """ + class FtsDictionaryModule(SchemaChildModule) + + A module class for FTS Dictionary node derived from SchemaChildModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the FtsDictionaryModule and + it's base module. + + * get_nodes(gid, sid, did, scid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node + + * script_load() + - Load the module script for FTS Dictionary, when any of the schema + node is initialized. + """ + NODE_TYPE = 'fts_dictionary' + COLLECTION_LABEL = _('FTS Dictionaries') + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + self.manager = None + super(FtsDictionaryModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + """ + yield self.generate_browser_collection_node(scid) + + @property + def node_inode(self): + """ + Override the property to make the node as leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for fts template, when any of the schema + node is initialized. + """ + return schemas.SchemaModule.NODE_TYPE + + +blueprint = FtsDictionaryModule(__name__) + + +class FtsDictionaryView(PGChildNodeView): + """ + class FtsDictionaryView(PGChildNodeView) + + A view class for FTS Dictionary node derived from PGChildNodeView. + This class is responsible for all the stuff related to view like + create/update/delete FTS Dictionary, + showing properties of node, showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the FtsDictionaryView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * tokenize_options(self, option_value): + - This function will tokenize the string stored in database + e.g. database store the value as below + key1=value1, key2=value2, key3=value3, .... + This function will extract key and value from above string + + * list() + - This function is used to list all the nodes within that collection. + + * nodes() + - This function will be used to create all the child node within collection. + Here it will create all the FTS Dictionary nodes. + + * node() + - This function will be used to create a node given its oid + Here it will create the FTS Template node based on its oid + + * properties(gid, sid, did, scid, dcid) + - This function will show the properties of the selected FTS Dictionary node + + * create(gid, sid, did, scid) + - This function will create the new FTS Dictionary object + + * update(gid, sid, did, scid, dcid) + - This function will update the data for the selected FTS Dictionary node + + * delete(self, gid, sid, did, scid, dcid): + - This function will drop the FTS Dictionary object + + * msql(gid, sid, did, scid, dcid) + - This function is used to return modified SQL for the selected node + + * get_sql(data, dcid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid, dcid): + - This function will generate sql to show in sql pane for node. + + * fetch_templates(): + - This function will fetch all templates related to node + + * dependents(gid, sid, did, scid, dcid): + - This function get the dependents and return ajax response for the node. + + * dependencies(self, gid, sid, did, scid, dcid): + - This function get the dependencies and return ajax response for node. + + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'dcid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'fetch_templates': [{'get': 'fetch_templates'}, + {'get': 'fetch_templates'}], + }) + + def _init_(self, **kwargs): + self.conn = None + self.template_path = None + self.manager = None + super(FtsDictionaryView, self).__init__(**kwargs) + + def module_js(self): + """ + Load JS file (fts_dictionary.js) for this module. + """ + return make_response( + render_template( + "fts_dictionary/js/fts_dictionary.js", + _=_ + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + _("Connection to the server has been lost!") + ) + # we will set template path for sql scripts depending upon server version + ver = self.manager.version + if ver >= 90100: + self.template_path = 'fts_dictionary/sql/9.1_plus' + return f(*args, **kwargs) + + return wrap + + def tokenize_options(self, option_value): + """ + This function will tokenize the string stored in database + e.g. database store the value as below + key1=value1, key2=value2, key3=value3, .... + This function will extract key and value from above string + + Args: + option_value: key value option/value pair read from database + """ + if option_value is not None: + option_str = option_value.split(',') + options = [] + for fdw_option in option_str: + k, v = fdw_option.split('=', 1) + options.append({'option': k, 'value': v}) + return options + + + @check_precondition + def list(self, gid, sid, did, scid): + """ + List all FTS Dictionary nodes. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + """ + + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + scid=scid + ) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + if row['options'] is not None: + row['options'] = self.tokenize_options(row['options']) + + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + Return all FTS Dictionaries to generate nodes. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + """ + + res = [] + sql = render_template( + "/".join([self.template_path, 'nodes.sql']), + scid=scid + ) + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + did, + row['name'], + icon="icon-fts_dictionary" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def node(self, gid, sid, did, scid, dcid): + """ + Return FTS Dictionary node to generate node + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + dcid: fts dictionary id + """ + + sql = render_template( + "/".join([self.template_path, 'nodes.sql']), + dcid=dcid + ) + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + if len(rset['rows']) == 0: + return gone(_(""" + Could not find the FTS Dictionary node. + """)) + + for row in rset['rows']: + return make_json_response( + data=self.blueprint.generate_browser_node( + row['oid'], + did, + row['name'], + icon="icon-fts_dictionary" + ), + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, dcid): + """ + Show properties of FTS Dictionary node + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + dcid: fts dictionary id + """ + + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + scid=scid, + dcid=dcid + ) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(_(""" + Could not find the FTS Dictionary node. + """)) + + if res['rows'][0]['options'] is not None: + res['rows'][0]['options'] = self.tokenize_options(res['rows'][0]['options']) + + return ajax_response( + response=res['rows'][0], + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will creates new the FTS Dictionary object + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + """ + + # Mandatory fields to create a new FTS Dictionary + required_args = [ + 'template', + 'schema', + 'name' + ] + + data = request.form if request.form else json.loads( + request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=_( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + # Fetch schema name from schema oid + sql = render_template("/".join([self.template_path, 'schema.sql']), + data=data, + conn=self.conn, + ) + + status, schema = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=schema) + + # Replace schema oid with schema name before passing to create.sql + # To generate proper sql query + new_data = data.copy() + new_data['schema'] = schema + sql = render_template("/".join([self.template_path, 'create.sql']), + data=new_data, + conn=self.conn, + ) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + # We need dcid to add object in tree at browser, + # Below sql will give the same + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + name=data['name'] + ) + status, dcid = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=dcid) + + return jsonify( + node=self.blueprint.generate_browser_node( + dcid, + did, + data['name'], + icon="icon-fts_dictionary" + ) + ) + except Exception as e: + current_app.logger.exception(e) + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, dcid): + """ + This function will update FTS Dictionary object + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + :param dcid: fts dictionary id + """ + data = request.form if request.form else json.loads( + request.data.decode()) + + # Fetch sql query to update fts dictionary + sql = self.get_sql(gid, sid, did, scid, data, dcid) + try: + if sql and sql.strip('\n') and sql.strip(' '): + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + if dcid is not None: + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + dcid=dcid, + scid=scid + ) + + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(_(""" + Could not find the FTS Dictionary node to update. + """)) + + data = res['rows'][0] + return jsonify( + node=self.blueprint.generate_browser_node( + dcid, + did, + data['name'], + icon="icon-fts_dictionary" + ) + ) + # In case FTS Dictionary node is not present + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': dcid, + 'sid': sid, + 'gid': gid, + 'did': did, + 'scid': scid + } + ) + + except Exception as e: + current_app.logger.exception(e) + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, dcid): + """ + This function will drop the FTS Dictionary object + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + :param dcid: FTS Dictionary id + """ + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + # Get name for FTS Dictionary from dcid + sql = render_template("/".join([self.template_path, 'delete.sql']), + dcid=dcid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(_(""" + Could not find the FTS Dictionary node to delete. + """)) + + # Drop FTS Dictionary + result = res['rows'][0] + sql = render_template("/".join([self.template_path, 'delete.sql']), + name=result['name'], + schema=result['schema'], + cascade=cascade + ) + + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("FTS Dictionary dropped"), + data={ + 'id': dcid, + 'sid': sid, + 'gid': gid, + 'did': did, + 'scid': scid + } + ) + + except Exception as e: + current_app.logger.exception(e) + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, dcid=None): + """ + This function returns modified SQL + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + :param dcid: FTS Dictionary id + """ + #data = request.args + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + # Fetch sql query for modified data + sql = self.get_sql(gid, sid, did, scid, data, dcid) + + if isinstance(sql, str) and sql and sql.strip('\n') and sql.strip(' '): + return make_json_response( + data=sql, + status=200 + ) + else: + return make_json_response( + data="--modified SQL", + status=200 + ) + + def get_sql(self, gid, sid, did, scid, data, dcid=None): + """ + This function will return SQL for model data + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + :param dcid: fts dictionary id + """ + try: + # Fetch sql for update + if dcid is not None: + sql = render_template( + "/".join([self.template_path, 'properties.sql']), + dcid=dcid, + scid=scid + ) + + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(_(""" + Could not find the FTS Dictionary node. + """)) + + old_data = res['rows'][0] + + # If user has changed the schema then fetch new schema directly + # using its oid otherwise fetch old schema name using its oid + sql = render_template( + "/".join([self.template_path, 'schema.sql']), + data=data) + + status, new_schema = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=new_schema) + + # Replace schema oid with schema name + new_data = data.copy() + if 'schema' in new_data: + new_data['schema'] = new_schema + + # Fetch old schema name using old schema oid + sql = render_template( + "/".join([self.template_path, 'schema.sql']), + data=old_data) + + status, old_schema = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=old_schema) + + # Replace old schema oid with old schema name + old_data['schema'] = old_schema + + sql = render_template( + "/".join([self.template_path, 'update.sql']), + data=new_data, o_data=old_data + ) + # Fetch sql query for modified data + else: + # Fetch schema name from schema oid + sql = render_template("/".join([self.template_path, 'schema.sql']), + data=data) + + status, schema = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=schema) + + # Replace schema oid with schema name + new_data = data.copy() + new_data['schema'] = schema + + if 'template' in new_data and \ + 'name' in new_data and \ + 'schema' in new_data: + sql = render_template("/".join([self.template_path, + 'create.sql']), + data=new_data, + conn=self.conn + ) + else: + sql = "-- incomplete definition" + return str(sql.strip('\n')) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def fetch_templates(self, gid, sid, did, scid): + """ + This function will return templates list for FTS Dictionary + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + """ + # Fetch last system oid + datlastsysoid = self.manager.db_info[did]['datlastsysoid'] + + sql = render_template("/".join([self.template_path, 'templates.sql']), + template=True) + status, rset = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=rset) + + # Empty set is added before actual list as initially it will be visible + # at template control while creating a new FTS Dictionary + res = [{'label': '', 'value': ''}] + for row in rset['rows']: + if row['schemaoid'] > datlastsysoid : + row['tmplname'] = row['nspname'] + '.' + row['tmplname'] + + res.append({'label': row['tmplname'], + 'value': row['tmplname']}) + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, dcid): + """ + This function will reverse generate sql for sql panel + :param gid: group id + :param sid: server id + :param did: database id + :param scid: schema id + :param dcid: FTS Dictionary id + """ + try: + sql = render_template( + "/".join([self.template_path, 'sql.sql']), + dcid=dcid, + scid=scid, + conn=self.conn + ) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error( + _( + "ERROR: Couldn't generate reversed engineered query for the FTS Dictionary!\n{0}").format( + res + ) + ) + + if res is None: + return gone( + _( + "ERROR: Couldn't generate reversed engineered query for FTS Dictionary node!") + ) + + return ajax_response(response=res) + + except Exception as e: + current_app.logger.exception(e) + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, dcid): + """ + This function get the dependents and return ajax response + for the FTS Dictionary node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + dcid: FTS Dictionary ID + """ + dependents_result = self.get_dependents(self.conn, dcid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, dcid): + """ + This function get the dependencies and return ajax response + for the FTS Dictionary node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + dcid: FTS Dictionary ID + """ + dependencies_result = self.get_dependencies(self.conn, dcid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +FtsDictionaryView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js new file mode 100644 index 000000000..0a9baef93 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/js/fts_dictionary.js @@ -0,0 +1,185 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, alertify) { + + // Extend the browser's node model class to create a option/value pair + var OptionLabelModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + options: undefined, + value: undefined + }, + // Define the schema for the Options + schema: [ + { + id: 'option', label:'Option', type:'text', group: null, + cellHeaderClasses:'width_percent_50', editable: true + },{ + id: 'value', label:'Value', type: 'text', group:null, + cellHeaderClasses:'width_percent_50', editable: true + }, + ], + validate: function() { + // Clear any existing errors. + this.errorModel.clear() + + if (_.isUndefined(this.get('option')) || + String(this.get('option')).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Option can not be empty!') }}'; + this.errorModel.set('option',msg); + return msg; + } + if (_.isUndefined(this.get('value')) || + String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Value can not be empty!') }}'; + this.errorModel.set('value',msg); + return msg; + } + return null; + } + }); + + // Extend the collection class for FTS Dictionary + if (!pgBrowser.Nodes['coll-fts_dictionary']) { + var fts_dictionaries = pgAdmin.Browser.Nodes['coll-fts_dictionary'] = + pgAdmin.Browser.Collection.extend({ + node: 'fts_dictionary', + label: '{{ _('FTS Dictionaries') }}', + type: 'coll-fts_dictionary', + columns: ['name', 'description'] + }); + }; + + // Extend the node class for FTS Dictionary + if (!pgBrowser.Nodes['fts_dictionary']) { + pgAdmin.Browser.Nodes['fts_dictionary'] = pgAdmin.Browser.Node.extend({ + parent_type: ['schema', 'catalog'], + type: 'fts_dictionary', + sqlAlterHelp: 'sql-altertsdictionary.html', + sqlCreateHelp: 'sql-createtsdictionary.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 Dictionary + pgBrowser.add_menus([{ + name: 'create_fts_dictionary_on_schema', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{_('FTS Dictionary...')}}', + icon: 'wcTabIcon icon-fts_dictionary', data: {action: 'create'} + },{ + name: 'create_fts_dictionary_on_coll', node: 'coll-fts_dictionary', + module: this, applies: ['object', 'context'], priority: 4, + callback: 'show_obj_properties', category: 'create', + label: '{{ _('FTS Dictionary...') }}', data: {action: 'create'}, + icon: 'wcTabIcon icon-fts_dictionary' + },{ + name: 'create_fts_dictionary', node: 'fts_dictionary', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{_('FTS Dictionary...')}}', + icon: 'wcTabIcon icon-fts_dictionary', data: {action: 'create'} + }]); + }, + + // Defining backform model for FTS Dictionary node + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, // FTS Dictionary name + owner: undefined, // FTS Dictionary owner + description: undefined, // Comment on FTS Dictionary + schema: undefined, // Schema name FTS dictionary belongs to + template: undefined, // Template list for FTS dictionary node + options: undefined // option/value pair list for FTS Dictionary + }, + initialize: function(attrs, args) { + var isNew = (_.size(attrs) === 0); + + if (isNew) { + var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user; + this.set({'owner': userInfo.name}, {silent: true}); + } + pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments); + if (_.isUndefined(this.get('schema'))) { + this.set('schema', this.node_info.schema._id); + } + }, + // Defining schema for fts dictionary + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text', cellHeaderClasses: 'width_percent_50' + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + editable: false, type: 'text', disabled: true, mode:['properties'] + },{ + id: 'owner', label:'{{ _('Owner') }}', cell: 'string', + type: 'text', mode: ['properties', 'edit','create'], node: 'role', + control: Backform.NodeListByNameControl + },{ + id: 'schema', label: '{{ _('Schema')}}', cell: 'string', + type: 'text', mode: ['create','edit'], node: 'schema', + control: 'node-list-by-id' + },{ + id: 'description', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', cellHeaderClasses: 'width_percent_50' + },{ + id: 'template', label: '{{ _('Template')}}',type: 'text', + disabled: function(m) { return !m.isNew(); }, url: 'fetch_templates', + group: '{{ _('Definition') }}',control: 'node-ajax-options' + },{ + id: 'options', label: '{{ _('Option') }}', type: 'collection', + group: '{{ _('Options') }}', control: 'unique-col-collection', + model: OptionLabelModel, columns: ['option', 'value'], + uniqueCol : ['option'], mode: ['edit', 'create'], + canAdd: true, canEdit: false,canDelete: true + }], + + /* + * Triggers control specific error messages for dictionary name, + * template and schema, if any one of them is not specified + * while creating new fts dictionary + */ + validate: function(keys){ + var name = this.get('name'); + var template = this.get('template');; + var schema = this.get('schema'); + + // Validate FTS Dictionary name + if (_.isUndefined(name) || _.isNull(name) || String(name).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Name must be specified!') }}'; + this.errorModel.set('name', msg); + return msg; + } + + // Validate template name + else if (_.isUndefined(template) || _.isNull(template) || String(template).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Template must be selected!') }}'; + this.errorModel.set('template', msg); + return msg; + } + + // Validate schema + else if (_.isUndefined(schema) || _.isNull(schema) || String(schema).replace(/^\s+|\s+$/g, '') == '') { + var msg = '{{ _('Schema must be selected!') }}'; + this.errorModel.set('schema', msg); + return msg; + } + else this.errorModel.clear(); + + this.trigger('on-status-clear'); + return null; + } + }) + }); + } + +return pgBrowser.Nodes['coll-fts_dictionary']; +}); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql new file mode 100644 index 000000000..94cb11fcb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/create.sql @@ -0,0 +1,13 @@ +{# CREATE FTS DICTIONARY Statement #} +{% if data and data.schema and data.name and data.template %} +CREATE TEXT SEARCH DICTIONARY {{ conn|qtIdent(data.schema, data.name) }} ( + TEMPLATE = {{ data.template }}{% for variable in data.options %}{% if "option" in variable and variable.option != '' %}, + {{ conn|qtIdent(variable.option) }} = {{ variable.value|qtLiteral }}{% endif %}{% endfor %} + +); +{# Description for FTS_DICTIONARY #} + +{% if data.description %} +COMMENT ON TEXT SEARCH DICTIONARY {{ conn|qtIdent(data.schema, data.name) }} + IS {{ data.description|qtLiteral }}; +{% endif %}{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..c344a7d9b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/delete.sql @@ -0,0 +1,23 @@ +{# FETCH FTS DICTIONARY NAME Statement #} +{% if dcid %} +SELECT + dict.dictname as name, + ( + SELECT + nspname + FROM + pg_namespace + WHERE + oid = dict.dictnamespace + ) as schema +FROM + pg_ts_dict dict LEFT OUTER JOIN pg_description des + ON (des.objoid=dict.oid AND des.classoid='pg_ts_dict'::regclass) +WHERE + dict.oid = {{dcid}}::OID; +{% endif %} + +{# DROP FTS DICTIOANRY Statement #} +{% if schema and name %} +DROP TEXT SEARCH DICTIONARY {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} {% if cascade %}CASCADE{%endif%}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..78abd0b0b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/nodes.sql @@ -0,0 +1,13 @@ +{# FETCH FTS DICTIONARY name statement #} +SELECT + oid, dictname as name +FROM + pg_ts_dict dict +WHERE +{% if scid %} + dict.dictnamespace = {{scid}}::OID +{% elif dcid %} + dict.oid = {{pid}}::OID +{% endif %} + +ORDER BY name \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..16e564418 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/properties.sql @@ -0,0 +1,24 @@ +{# FETCH properties for FTS DICTIONARY #} +SELECT + dict.oid, + dict.dictname as name, + pg_get_userbyid(dict.dictowner) as owner, + t.tmplname as template, + dict.dictinitoption as options, + dict.dictnamespace as schema, + des.description +FROM + pg_ts_dict dict + LEFT OUTER JOIN pg_ts_template t ON t.oid=dict.dicttemplate + LEFT OUTER JOIN pg_description des ON (des.objoid=dict.oid AND des.classoid='pg_ts_dict'::regclass) +WHERE +{% if scid %} + dict.dictnamespace = {{scid}}::OID +{% elif name %} +dict.dictname = {{name|qtLiteral}} +{% endif %} +{% if dcid %} + AND dict.oid = {{dcid}}::OID +{% endif %} +ORDER BY + dict.dictname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql new file mode 100644 index 000000000..2a38e7f91 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/schema.sql @@ -0,0 +1,19 @@ +{# FETCH statement for SCHEMA name #} +{% if data.schema %} +SELECT + nspname +FROM + pg_namespace +WHERE + oid = {{data.schema}}::OID + +{% elif data.id %} +SELECT + nspname +FROM + pg_namespace nsp + LEFT JOIN pg_ts_dict dict + ON dict.dictnamespace = nsp.oid +WHERE + dict.oid = {{data.id}}::OID +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql new file mode 100644 index 000000000..42537f681 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/sql.sql @@ -0,0 +1,52 @@ +{# REVERSED ENGINEERED SQL FOR FTS DICTIONARY #} +{% if dcid and scid %} +SELECT + array_to_string(array_agg(sql), E'\n\n') as sql +FROM + ( + SELECT + E'-- Text Search Dictionary: ' || nspname || E'.' || dict.dictname || + E'\n\n-- DROP TEXT SEARCH DICTIONARY ' || nspname || E'.' || dict.dictname || + E'\n\nCREATE TEXT SEARCH DICTIONARY ' || nspname || E'.' || dict.dictname || E' (\n' || + E'\tTEMPLATE = ' || template || + CASE + WHEN dict.dictinitoption IS NOT NULL THEN E',\n\t' || dict.dictinitoption + ELSE '' + END || + E'\n);' || + CASE + WHEN description IS NOT NULL THEN + E'\n\nCOMMENT ON TEXT SEARCH TEMPLATE ' || nspname || E'.' || dict.dictname || + E' IS ' || pg_catalog.quote_literal(description) || E';' + ELSE '' END as sql + FROM + pg_ts_dict dict + LEFT JOIN( + SELECT + t.tmplname as template, + t.oid as oid + FROM + pg_ts_template t + ) d on d.oid = dict.dicttemplate + LEFT JOIN ( + SELECT + des.description as description, + des.objoid as descoid + FROM + pg_description des + WHERE + des.objoid={{dcid}}::OID AND des.classoid='pg_ts_dict'::regclass + ) a ON (a.descoid = dict.oid) + LEFT JOIN ( + SELECT + nspname, + nsp.oid as noid + FROM + pg_namespace nsp + WHERE + oid = {{scid}}::OID + ) b ON (b.noid = dict.dictnamespace) +WHERE + dict.oid={{dcid}}::OID +) as c; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql new file mode 100644 index 000000000..d3270ba01 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/templates.sql @@ -0,0 +1,11 @@ +{# FETCH templates for FTS DICTIONARY #} +{% if template %} +SELECT + tmplname, + nspname, + n.oid as schemaoid +FROM + pg_ts_template JOIN pg_namespace n ON n.oid=tmplnamespace +ORDER BY + tmplname +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql new file mode 100644 index 000000000..51ba1793a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/templates/fts_dictionary/sql/9.1_plus/update.sql @@ -0,0 +1,49 @@ +{# UPDATE statement for FTS DICTIONARY #} +{% if data %} +{% set name = o_data.name %} +{% set schema = o_data.schema %} +{% if data.name and data.name != o_data.name %} +{% set name = data.name %} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}} + RENAME TO {{data.name}}; + +{% endif %} +{% if 'options' in data %} +{% if'changed' in data.options %} +{% for opt in data.options.changed %} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}} + ({{opt.option}}={{opt.value}}); + +{% endfor %} +{% endif %} +{% if'added' in data.options%} +{% for opt in data.options.added %} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}} + ({{opt.option}}={{opt.value}}); + +{% endfor %} +{% endif %} +{% if'deleted' in data.options%} +{% for opt in data.options.deleted %} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}} + ({{opt.option}}); + +{% endfor %} +{% endif %} +{% endif %} +{% if 'owner' in data and data.owner != o_data.owner %} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}} + OWNER TO {{data.owner}}; + +{% endif %} +{% if 'schema' in data and data.schema != o_data.schema %} +{% set schema = data.schema%} +ALTER TEXT SEARCH DICTIONARY {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}} + SET SCHEMA {{data.schema}}; + +{% endif %} +{% if 'description' in data and data.description != o_data.description %} +COMMENT ON TEXT SEARCH DICTIONARY {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} + IS {{ data.description|qtLiteral }}; +{% endif %} +{% endif %} \ No newline at end of file