Added support for FTS configuration node
parent
7b2e4fb467
commit
6895da9cbc
|
@ -0,0 +1,939 @@
|
|||
##########################################################################
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
Binary file not shown.
After Width: | Height: | Size: 369 B |
Binary file not shown.
After Width: | Height: | Size: 346 B |
|
@ -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-3">',
|
||||
' <label class="control-label"><%-token_label%></label>',
|
||||
' </div>',
|
||||
' <div class="col-xs-6" header="token"></div>',
|
||||
' <div class="col-xs-2">',
|
||||
' <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, select2: { allowClear: false }
|
||||
},{
|
||||
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'];
|
||||
});
|
|
@ -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 %}
|
|
@ -0,0 +1,19 @@
|
|||
{# 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 %}
|
||||
);
|
||||
|
||||
{% if 'owner' in data and data.owner != '' %}
|
||||
ALTER TEXT SEARCH CONFIGURATION {{ conn|qtIdent(data.schema, data.name) }} OWNER TO {{ conn|qtIdent(data.owner) }};
|
||||
{% 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 %}
|
|
@ -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 %}
|
|
@ -0,0 +1,7 @@
|
|||
{# FETCH DICTIONARIES statement #}
|
||||
SELECT
|
||||
dictname
|
||||
FROM
|
||||
pg_ts_dict
|
||||
ORDER BY
|
||||
dictname
|
|
@ -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 %}
|
|
@ -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
|
|
@ -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 %}
|
|
@ -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
|
|
@ -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 %}
|
|
@ -0,0 +1,76 @@
|
|||
{# 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 || E'\n' ||
|
||||
|
||||
array_to_string(
|
||||
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)
|
||||
, E'\n') 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 %}
|
|
@ -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 %}
|
|
@ -0,0 +1,10 @@
|
|||
{# Tokens for FTS CONFIGURATION node #}
|
||||
|
||||
{% if parseroid %}
|
||||
SELECT
|
||||
alias
|
||||
FROM
|
||||
ts_token_type({{parseroid}}::OID)
|
||||
ORDER BY
|
||||
alias
|
||||
{% endif %}
|
|
@ -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 != '' 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 %}
|
|
@ -59,7 +59,7 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||
sqlCreateHelp: 'sql-createtsdictionary.html',
|
||||
canDrop: true,
|
||||
canDropCascade: true,
|
||||
label: '{{ _('FTS dictionaries') }}',
|
||||
label: '{{ _('FTS dictionary') }}',
|
||||
hasSQL: true,
|
||||
hasDepends: true,
|
||||
Init: function() {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue