mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add support for Event Triggers.
This commit is contained in:
parent
3b03c17f2b
commit
986375d60e
@ -0,0 +1,664 @@
|
|||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from flask import render_template, make_response, request, jsonify
|
||||||
|
from flask.ext.babel import gettext
|
||||||
|
from pgadmin.utils.ajax import make_json_response, \
|
||||||
|
make_response as ajax_response, internal_server_error
|
||||||
|
from pgadmin.browser.utils import PGChildNodeView
|
||||||
|
from pgadmin.browser.collection import CollectionNodeModule
|
||||||
|
import pgadmin.browser.server_groups.servers.databases as database
|
||||||
|
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 EventTriggerModule(CollectionNodeModule):
|
||||||
|
"""
|
||||||
|
class EventTriggerModule(CollectionNodeModule)
|
||||||
|
|
||||||
|
A module class for Event trigger node derived from CollectionNodeModule.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
-------
|
||||||
|
* __init__(*args, **kwargs)
|
||||||
|
- Method is used to initialize the EventTriggerModule and it's base module.
|
||||||
|
|
||||||
|
* get_nodes(gid, sid, did)
|
||||||
|
- Method is used to generate the browser collection node.
|
||||||
|
|
||||||
|
* script_load()
|
||||||
|
- Load the module script for Event trigger, when any of the database node is
|
||||||
|
initialized.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NODE_TYPE = 'event_trigger'
|
||||||
|
COLLECTION_LABEL = gettext("Event Triggers")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Method is used to initialize the EventTriggerModule and it's base module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args:
|
||||||
|
**kwargs:
|
||||||
|
"""
|
||||||
|
super(EventTriggerModule, self).__init__(*args, **kwargs)
|
||||||
|
self.min_ver = 90300
|
||||||
|
self.max_ver = None
|
||||||
|
|
||||||
|
def get_nodes(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
Generate the event_trigger node
|
||||||
|
"""
|
||||||
|
yield self.generate_browser_collection_node(sid)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_inode(self):
|
||||||
|
"""
|
||||||
|
If a node have child return True otherwise False
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def script_load(self):
|
||||||
|
"""
|
||||||
|
Load the module script for event_trigger, when any of the database node is
|
||||||
|
initialized.
|
||||||
|
"""
|
||||||
|
return database.DatabaseModule.NODE_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
blueprint = EventTriggerModule(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EventTriggerView(PGChildNodeView):
|
||||||
|
"""
|
||||||
|
class EventTriggerView(PGChildNodeView)
|
||||||
|
|
||||||
|
A view class for event trigger node derived from PGChildNodeView.
|
||||||
|
This class is responsible for all the stuff related to view like
|
||||||
|
updating event trigger node, showing properties, showing sql in sql
|
||||||
|
pane.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
-------
|
||||||
|
* __init__(**kwargs)
|
||||||
|
- Method is used to initialize the EventTriggerView 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 event trigger nodes within
|
||||||
|
that collection.
|
||||||
|
|
||||||
|
* nodes()
|
||||||
|
- This function will used to create all the child node within that collection.
|
||||||
|
Here it will create all the event trigger node.
|
||||||
|
|
||||||
|
* properties(gid, sid, did, etid)
|
||||||
|
- This function will show the properties of the selected
|
||||||
|
event trigger node
|
||||||
|
|
||||||
|
* update(gid, sid, did, etid)
|
||||||
|
- This function will update the data for the selected event trigger node.
|
||||||
|
|
||||||
|
* msql(gid, sid, did, etid)
|
||||||
|
- This function is used to return modified SQL for the selected
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
* get_sql(data, etid)
|
||||||
|
- This function will generate sql from model data
|
||||||
|
|
||||||
|
* sql(gid, sid, did, etid):
|
||||||
|
- This function will generate sql to show it in sql pane for the selected
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
* get_event_funcs(gid, sid, did, etid):
|
||||||
|
- This function gets the event functions and returns an ajax response
|
||||||
|
for the event trigger node.
|
||||||
|
|
||||||
|
* dependents(gid, sid, did, etid):
|
||||||
|
- This function get the dependents and return ajax response for the
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
* dependencies(self, gid, sid, did, etid):
|
||||||
|
- This function get the dependencies and return ajax response for the
|
||||||
|
event trigger node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
node_type = blueprint.node_type
|
||||||
|
|
||||||
|
parent_ids = [
|
||||||
|
{'type': 'int', 'id': 'gid'},
|
||||||
|
{'type': 'int', 'id': 'sid'},
|
||||||
|
{'type': 'int', 'id': 'did'}
|
||||||
|
]
|
||||||
|
ids = [
|
||||||
|
{'type': 'int', 'id': 'etid'}
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = dict({
|
||||||
|
'obj': [
|
||||||
|
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
|
||||||
|
{'get': 'list', 'post': 'create'}
|
||||||
|
],
|
||||||
|
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||||
|
'children': [{'get': 'children'}],
|
||||||
|
'sql': [{'get': 'sql'}],
|
||||||
|
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||||
|
'stats': [{'get': 'statistics'}],
|
||||||
|
'dependency': [{'get': 'dependencies'}],
|
||||||
|
'dependent': [{'get': 'dependents'}],
|
||||||
|
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||||
|
'fopts': [{'get': 'get_event_funcs'}, {'get': 'get_event_funcs'}]
|
||||||
|
})
|
||||||
|
|
||||||
|
def module_js(self):
|
||||||
|
"""
|
||||||
|
This property defines whether javascript exists for this node.
|
||||||
|
"""
|
||||||
|
return make_response(
|
||||||
|
render_template(
|
||||||
|
"event_triggers/js/event_trigger.js",
|
||||||
|
_=gettext
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
gettext(
|
||||||
|
"Connection to the server has been lost!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ver = self.manager.version
|
||||||
|
if ver >= 90300:
|
||||||
|
self.template_path = 'event_triggers/sql/9.3_plus'
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def list(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
This function is used to list all the event trigger
|
||||||
|
nodes within that collection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
sql = render_template("/".join([self.template_path, 'properties.sql']))
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
This function is used to create all the child nodes within the collection.
|
||||||
|
Here it will create all the event trigger nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
sql = render_template("/".join([self.template_path, 'nodes.sql']))
|
||||||
|
status, res = self.conn.execute_2darray(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
for row in res['rows']:
|
||||||
|
result.append(
|
||||||
|
self.blueprint.generate_browser_node(
|
||||||
|
row['oid'],
|
||||||
|
did,
|
||||||
|
row['name'],
|
||||||
|
icon="icon-%s" % self.node_type
|
||||||
|
))
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
data=result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def properties(self, gid, sid, did, etid):
|
||||||
|
"""
|
||||||
|
This function is used to list all the event trigger
|
||||||
|
nodes within that collection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
sql = render_template("/".join([self.template_path, 'properties.sql']), etid=etid, conn=self.conn)
|
||||||
|
status, res = self.conn.execute_dict(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
result = res['rows'][0]
|
||||||
|
sec_labels = []
|
||||||
|
|
||||||
|
if 'seclabels' in result and result['seclabels'] is not None:
|
||||||
|
for sec in result['seclabels']:
|
||||||
|
sec = re.search(r'([^=]+)=(.*$)', sec)
|
||||||
|
sec_labels.append({
|
||||||
|
'provider': sec.group(1),
|
||||||
|
'securitylabel': sec.group(2)
|
||||||
|
})
|
||||||
|
result.update({"seclabels": sec_labels})
|
||||||
|
return ajax_response(
|
||||||
|
response=result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def create(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
This function will create a event trigger object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
data = request.form if request.form else json.loads(request.data.decode())
|
||||||
|
required_args = {
|
||||||
|
'name':'Name',
|
||||||
|
'eventowner':'Owner',
|
||||||
|
'eventfunname':'Trigger function',
|
||||||
|
'enabled':'Enabled status',
|
||||||
|
'eventname':'Events'
|
||||||
|
}
|
||||||
|
err = []
|
||||||
|
for arg in required_args:
|
||||||
|
if arg not in data:
|
||||||
|
err.append(required_args.get(arg, arg))
|
||||||
|
if err:
|
||||||
|
return make_json_response(
|
||||||
|
status=400,
|
||||||
|
success=0,
|
||||||
|
errormsg=gettext(
|
||||||
|
"Couldn't find the required parameter/s %s." % err
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
|
||||||
|
status, res = self.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
sql = render_template("/".join([self.template_path, 'grant.sql']), data=data, conn=self.conn)
|
||||||
|
sql = sql.strip('\n').strip(' ')
|
||||||
|
|
||||||
|
status, res = self.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
sql = render_template("/".join([self.template_path, 'get_oid.sql']), data=data)
|
||||||
|
status, etid = self.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=etid)
|
||||||
|
|
||||||
|
return jsonify(
|
||||||
|
node=self.blueprint.generate_browser_node(
|
||||||
|
etid,
|
||||||
|
did,
|
||||||
|
data['name'],
|
||||||
|
icon="icon-%s" % self.node_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def update(self, gid, sid, did, etid):
|
||||||
|
"""
|
||||||
|
This function will update the data for the selected
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
data = request.form if request.form else json.loads(request.data.decode())
|
||||||
|
|
||||||
|
try:
|
||||||
|
sql = self.get_sql(data, etid)
|
||||||
|
sql = sql.strip('\n').strip(' ')
|
||||||
|
if sql != "":
|
||||||
|
status, res = self.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
sql = render_template("/".join([self.template_path, 'get_oid.sql']), data=data)
|
||||||
|
status, etid = self.conn.execute_scalar(sql)
|
||||||
|
|
||||||
|
return jsonify(
|
||||||
|
node=self.blueprint.generate_browser_node(
|
||||||
|
etid,
|
||||||
|
did,
|
||||||
|
data['name'],
|
||||||
|
icon="icon-%s" % self.node_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info="Nothing to update",
|
||||||
|
data={
|
||||||
|
'id': etid,
|
||||||
|
'sid': sid,
|
||||||
|
'gid': gid,
|
||||||
|
'did': did
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def delete(self, gid, sid, did, etid):
|
||||||
|
"""
|
||||||
|
This function will delete an existing event trigger object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.cmd == 'delete':
|
||||||
|
# This is a cascade operation
|
||||||
|
cascade = True
|
||||||
|
else:
|
||||||
|
cascade = False
|
||||||
|
try:
|
||||||
|
sql = render_template("/".join([self.template_path, 'delete.sql']), etid=etid)
|
||||||
|
status, name = self.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=name)
|
||||||
|
|
||||||
|
sql = render_template("/".join([self.template_path, 'delete.sql']), name=name, 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=gettext("Event trigger dropped"),
|
||||||
|
data={
|
||||||
|
'id': etid,
|
||||||
|
'sid': sid,
|
||||||
|
'gid': gid,
|
||||||
|
'did': did
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def msql(self, gid, sid, did, etid = None):
|
||||||
|
"""
|
||||||
|
This function is used to return modified SQL for the selected
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
for k, v in request.args.items():
|
||||||
|
try:
|
||||||
|
data[k] = json.loads(v)
|
||||||
|
except ValueError:
|
||||||
|
data[k] = v
|
||||||
|
try:
|
||||||
|
sql = self.get_sql(data, etid)
|
||||||
|
sql = sql.strip('\n').strip(' ')
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
data=sql,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
def get_sql(self, data, etid=None):
|
||||||
|
"""
|
||||||
|
This function will generate sql from model data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Contains the data of the selected event trigger node.
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
required_args = [
|
||||||
|
'name'
|
||||||
|
]
|
||||||
|
|
||||||
|
if etid is not None:
|
||||||
|
sql = render_template("/".join([self.template_path, 'properties.sql']), etid=etid)
|
||||||
|
status, res = self.conn.execute_dict(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
old_data = res['rows'][0]
|
||||||
|
for arg in required_args:
|
||||||
|
if arg not in data:
|
||||||
|
data[arg] = old_data[arg]
|
||||||
|
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data)
|
||||||
|
else:
|
||||||
|
required_args = {
|
||||||
|
'name':'Name',
|
||||||
|
'eventowner':'Owner',
|
||||||
|
'eventfunname':'Trigger function',
|
||||||
|
'enabled':'Enabled status',
|
||||||
|
'eventname':'Events'
|
||||||
|
}
|
||||||
|
err = []
|
||||||
|
for arg in required_args:
|
||||||
|
if arg not in data:
|
||||||
|
err.append(required_args.get(arg, arg))
|
||||||
|
if err:
|
||||||
|
return make_json_response(
|
||||||
|
status=400,
|
||||||
|
success=0,
|
||||||
|
errormsg=gettext(
|
||||||
|
"Couldn't find the required parameter/s %s." % err
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sql = render_template("/".join([self.template_path, 'create.sql']), data=data)
|
||||||
|
sql += "\n"
|
||||||
|
sql += render_template("/".join([self.template_path, 'grant.sql']), data=data)
|
||||||
|
return sql
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def sql(self, gid, sid, did, etid):
|
||||||
|
"""
|
||||||
|
This function will generate sql to show in the sql pane for the selected
|
||||||
|
event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sql = render_template("/".join([self.template_path, 'properties.sql']), etid=etid)
|
||||||
|
status, res = self.conn.execute_dict(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
result = res['rows'][0]
|
||||||
|
|
||||||
|
sql = render_template("/".join([self.template_path, 'create.sql']), data=result, conn=self.conn)
|
||||||
|
sql += "\n\n"
|
||||||
|
sql += render_template("/".join([self.template_path, 'grant.sql']), data=result, conn=self.conn)
|
||||||
|
|
||||||
|
db_sql = render_template("/".join([self.template_path, 'get_db.sql']), did=did)
|
||||||
|
status, db_name = self.conn.execute_scalar(db_sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=db_name)
|
||||||
|
|
||||||
|
sql_header = "-- Event Trigger: {0} on database {1}\n\n-- ".format(result['name'], db_name)
|
||||||
|
|
||||||
|
sql_header += render_template(
|
||||||
|
"/".join([self.template_path, 'delete.sql']),
|
||||||
|
name=result['name'], )
|
||||||
|
sql_header += "\n"
|
||||||
|
|
||||||
|
sql = sql_header + sql
|
||||||
|
|
||||||
|
return ajax_response(response=sql)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return ajax_response(response=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def get_event_funcs(self, gid, sid, did, etid=None):
|
||||||
|
"""
|
||||||
|
This function gets the event functions and returns an ajax response
|
||||||
|
for the event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
res = [{ 'label': '', 'value': '' }]
|
||||||
|
sql = render_template("/".join([self.template_path, 'eventfunctions.sql']))
|
||||||
|
status, rest = self.conn.execute_2darray(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rest)
|
||||||
|
for row in rest['rows']:
|
||||||
|
res.append(
|
||||||
|
{'label': row['tfname'], 'value': row['tfname']}
|
||||||
|
)
|
||||||
|
return make_json_response(
|
||||||
|
data=res,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def dependents(self, gid, sid, did, etid=None):
|
||||||
|
"""
|
||||||
|
This function gets the dependents and returns an ajax response
|
||||||
|
for the event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
"""
|
||||||
|
dependents_result = self.get_dependents(self.conn, etid)
|
||||||
|
return ajax_response(
|
||||||
|
response=dependents_result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def dependencies(self, gid, sid, did, etid):
|
||||||
|
"""
|
||||||
|
This function gets the dependencies and returns an ajax response
|
||||||
|
for the event trigger node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
etid: Event trigger ID
|
||||||
|
"""
|
||||||
|
dependencies_result = self.get_dependencies(self.conn, etid)
|
||||||
|
return ajax_response(
|
||||||
|
response=dependencies_result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
EventTriggerView.register_node_view(blueprint)
|
Binary file not shown.
After Width: | Height: | Size: 350 B |
Binary file not shown.
After Width: | Height: | Size: 324 B |
Binary file not shown.
After Width: | Height: | Size: 610 B |
@ -0,0 +1,180 @@
|
|||||||
|
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 security model
|
||||||
|
var SecurityLabelModel = pgAdmin.Browser.Node.Model.extend({
|
||||||
|
defaults: {
|
||||||
|
provider: undefined,
|
||||||
|
securitylabel: undefined
|
||||||
|
},
|
||||||
|
// Define the schema for the Security Label
|
||||||
|
schema: [
|
||||||
|
{id: 'provider', label:'Provider', type:'text', group: null, editable: true},
|
||||||
|
{id: 'securitylabel', label:'Security Label', type: 'text', group:null, extraHeaderClasses: 'cellwidth-40', editable: true},
|
||||||
|
],
|
||||||
|
validate: function() {
|
||||||
|
// Clear any existing errors.
|
||||||
|
|
||||||
|
this.errorModel.clear()
|
||||||
|
if (_.isUndefined(this.get('provider')) || String(this.get('provider')).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
var msg = '{{ _('Provider can not be empty!') }}';
|
||||||
|
this.errorModel.set('provider',msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
if (_.isUndefined(this.get('securitylabel')) || String(this.get('securitylabel')).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
var msg = '{{ _('Security Label can not be empty!') }}';
|
||||||
|
this.errorModel.set('securitylabel',msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extend the browser's collection class for event trigger collection
|
||||||
|
if (!pgBrowser.Nodes['coll-event_trigger']) {
|
||||||
|
var databases = pgAdmin.Browser.Nodes['coll-event_trigger'] =
|
||||||
|
pgAdmin.Browser.Collection.extend({
|
||||||
|
node: 'event_trigger',
|
||||||
|
label: '{{ _('Event Trigger') }}',
|
||||||
|
type: 'coll-event_trigger',
|
||||||
|
columns: ['name', 'eventowner', 'comment']
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extend the browser's node class for event triggers node
|
||||||
|
if (!pgBrowser.Nodes['event_trigger']) {
|
||||||
|
pgAdmin.Browser.Nodes['event_trigger'] = pgAdmin.Browser.Node.extend({
|
||||||
|
parent_type: 'database',
|
||||||
|
type: 'event_trigger',
|
||||||
|
label: '{{ _('Event Trigger') }}',
|
||||||
|
hasSQL: true,
|
||||||
|
hasDepends: true,
|
||||||
|
canDrop: true,
|
||||||
|
Init: function() {
|
||||||
|
/* Avoid mulitple registration of menus */
|
||||||
|
if (this.initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
pgBrowser.add_menus([{
|
||||||
|
name: 'create_event_trigger_on_coll', node: 'coll-event_trigger', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||||
|
category: 'create', priority: 4, label: '{{ _('Event Trigger...') }}',
|
||||||
|
icon: 'wcTabIcon pg-icon-event_trigger', data: {action: 'create'}
|
||||||
|
},{
|
||||||
|
name: 'create_event_trigger', node: 'event_trigger', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||||
|
category: 'create', priority: 4, label: '{{ _('Event Trigger...') }}',
|
||||||
|
icon: 'wcTabIcon pg-icon-event_trigger', data: {action: 'create'}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
// Define the model for event trigger node
|
||||||
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
|
defaults: {
|
||||||
|
oid: undefined,
|
||||||
|
name: undefined,
|
||||||
|
eventowner: undefined,
|
||||||
|
comment: undefined,
|
||||||
|
enabled: undefined,
|
||||||
|
eventfuncoid: undefined,
|
||||||
|
eventfunname: undefined,
|
||||||
|
eventname: undefined,
|
||||||
|
when: undefined,
|
||||||
|
xmin: undefined,
|
||||||
|
source: undefined,
|
||||||
|
language: undefined
|
||||||
|
},
|
||||||
|
// Define the schema for the event trigger node
|
||||||
|
schema: [{
|
||||||
|
id: 'name', label: '{{ _('Name') }}', cell: 'string',
|
||||||
|
type: 'text'
|
||||||
|
},{
|
||||||
|
id: 'oid', label:'{{ _('OID') }}', cell: 'string',
|
||||||
|
type: 'text', mode: ['properties']
|
||||||
|
},{
|
||||||
|
id: 'eventowner', label:'{{ _('Owner') }}', cell: 'string',
|
||||||
|
type: 'text', mode: ['properties', 'edit','create'], node: 'role',
|
||||||
|
control: Backform.NodeListByNameControl
|
||||||
|
},{
|
||||||
|
id: 'comment', label:'{{ _('Comment') }}', type: 'multiline'
|
||||||
|
},{
|
||||||
|
id: 'enabled', label:'{{ _('Enabled status') }}',
|
||||||
|
type:"radio", group: "Definition", mode: ['properties', 'edit','create'],
|
||||||
|
options: [
|
||||||
|
{label: "Enable", value: "O"},
|
||||||
|
{label: "Disable", value: "D"},
|
||||||
|
{label: "Replica", value: "R"},
|
||||||
|
{label: "Always", value: "A"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
id: 'eventfunname', label:'{{ _('Trigger function') }}',
|
||||||
|
type: 'text', control: 'node-ajax-options', group: "Definition",
|
||||||
|
url:'fopts'
|
||||||
|
},{
|
||||||
|
id: 'eventname', label:'{{ _('Events') }}',
|
||||||
|
type:"radio", group: "Definition", cell: 'string',
|
||||||
|
options: [
|
||||||
|
{label: "DDL COMMAND START", value: "DDL_COMMAND_START"},
|
||||||
|
{label: "DDL COMMAND END", value: "DDL_COMMAND_END"},
|
||||||
|
{label: "SQL DROP", value: "SQL_DROP"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
id: 'when', label:'{{ _('When') }}', type: 'multiline', group: "Definition",
|
||||||
|
},{
|
||||||
|
id: 'providers', label: 'Security Labels', type: 'collection', group: "Security Labels",
|
||||||
|
model: SecurityLabelModel, control: 'unique-col-collection', mode: ['edit', 'create'],
|
||||||
|
canAdd: true, canDelete: true, uniqueCol : ['provider'],
|
||||||
|
columns: ['provider','securitylabel']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// event trigger model data validation.
|
||||||
|
validate: function() {
|
||||||
|
var msg = undefined;
|
||||||
|
// Clear any existing error msg.
|
||||||
|
this.errorModel.clear();
|
||||||
|
|
||||||
|
if (_.isUndefined(this.get('name'))
|
||||||
|
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
msg = '{{ _('Event trigger name can not be empty!') }}';
|
||||||
|
this.errorModel.set('name', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isUndefined(this.get('eventowner'))
|
||||||
|
|| String(this.get('eventowner')).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
msg = '{{ _('Event trigger owner can not be empty!') }}';
|
||||||
|
this.errorModel.set('eventowner', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isUndefined(this.get('enabled'))) {
|
||||||
|
msg = '{{ _('Event trigger enabled status can not be empty!') }}';
|
||||||
|
this.errorModel.set('enabled', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isUndefined(this.get('eventfunname'))
|
||||||
|
|| String(this.get('eventfunname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
msg = '{{ _('Event trigger function can not be empty!') }}';
|
||||||
|
this.errorModel.set('eventfunname', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isUndefined(this.get('eventname'))) {
|
||||||
|
msg = '{{ _('Event trigger event can not be empty!') }}';
|
||||||
|
this.errorModel.set('eventname', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgBrowser.Nodes['coll-event_trigger'];
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
{% if data %}
|
||||||
|
CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) }} ON {{data.eventname}}
|
||||||
|
{% if data.when %}
|
||||||
|
WHEN TAG IN ({{data.when}})
|
||||||
|
{% endif %}
|
||||||
|
EXECUTE PROCEDURE {{data.eventfunname}}();
|
||||||
|
{% endif %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% if etid %}
|
||||||
|
SELECT e.evtname AS name FROM pg_event_trigger e
|
||||||
|
WHERE e.oid={{etid}}::int;
|
||||||
|
{% endif %}
|
||||||
|
{% if name %}
|
||||||
|
DROP EVENT TRIGGER IF EXISTS {{ conn|qtIdent(name) }}{% if cascade%} CASCADE{% endif %};
|
||||||
|
{% endif %}
|
@ -0,0 +1,4 @@
|
|||||||
|
SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS tfname
|
||||||
|
FROM pg_proc p, pg_namespace n, pg_language l
|
||||||
|
WHERE p.pronamespace = n.oid AND p.prolang = l.oid AND p.pronargs = 0 AND l.lanname != 'sql' AND prorettype::regtype::text = 'event_trigger'
|
||||||
|
ORDER BY nspname ASC, proname ASC
|
@ -0,0 +1 @@
|
|||||||
|
SELECT db.datname as name FROM pg_database as db WHERE db.oid = {{did}}
|
@ -0,0 +1,5 @@
|
|||||||
|
{# The Sql below will provide oid for newly created event_trigger #}
|
||||||
|
{% if data %}
|
||||||
|
SELECT e.oid from pg_event_trigger e
|
||||||
|
WHERE e.evtname = {{ data.name|qtLiteral }}
|
||||||
|
{% endif %}
|
@ -0,0 +1,26 @@
|
|||||||
|
{% import 'macros/security.macros' as SECLABLE %}
|
||||||
|
{% if data %}
|
||||||
|
{% if data.enabled and data.enabled != "O" %}
|
||||||
|
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
{% if data.enabled == "D" %}
|
||||||
|
DISABLE;
|
||||||
|
{% elif data.enabled == "R" %}
|
||||||
|
ENABLE REPLICA;
|
||||||
|
{% elif data.enabled == "A" %}
|
||||||
|
ENABLE ALWAYS;
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.comment %}
|
||||||
|
COMMENT ON EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
IS {{ data.comment|qtLiteral }};
|
||||||
|
{% endif %}
|
||||||
|
{% if data.providers and data.providers|length > 0 %}
|
||||||
|
|
||||||
|
{% for r in data.providers %}
|
||||||
|
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
|
||||||
|
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
OWNER TO {{data.eventowner}};
|
||||||
|
{% endif %}
|
@ -0,0 +1,3 @@
|
|||||||
|
SELECT e.oid, e.evtname AS name
|
||||||
|
FROM pg_event_trigger e
|
||||||
|
ORDER BY e.evtname
|
@ -0,0 +1,17 @@
|
|||||||
|
SELECT e.oid, e.xmin, e.evtname AS name, upper(e.evtevent) AS eventname,
|
||||||
|
pg_catalog.pg_get_userbyid(e.evtowner) AS eventowner,
|
||||||
|
e.evtenabled AS enabled,
|
||||||
|
e.evtfoid AS eventfuncoid, quote_ident(n.nspname) || '.' || e.evtfoid::regproc AS eventfunname,
|
||||||
|
array_to_string(array(select quote_literal(x) from unnest(evttags) as t(x)), ', ') AS when,
|
||||||
|
pg_catalog.obj_description(e.oid, 'pg_event_trigger') AS comment,
|
||||||
|
(SELECT array_agg(provider || '=' || label) FROM pg_shseclabel sl1 WHERE sl1.objoid=e.oid) AS seclabels,
|
||||||
|
p.prosrc AS source, p.pronamespace AS schemaoid, l.lanname AS language
|
||||||
|
FROM pg_event_trigger e
|
||||||
|
LEFT OUTER JOIN pg_proc p ON p.oid=e.evtfoid
|
||||||
|
LEFT OUTER JOIN pg_language l ON l.oid=p.prolang,
|
||||||
|
pg_namespace n
|
||||||
|
WHERE p.pronamespace = n.oid
|
||||||
|
{% if etid %}
|
||||||
|
AND e.oid={{etid}}::int
|
||||||
|
{% endif %}
|
||||||
|
ORDER BY e.evtname
|
@ -0,0 +1,66 @@
|
|||||||
|
{% import 'macros/security.macros' as SECLABLE %}
|
||||||
|
{% if data %}
|
||||||
|
{% if (data.eventfunname and data.eventfunname != o_data.eventfunname) or
|
||||||
|
(data.eventname and data.eventname != o_data.eventname) or
|
||||||
|
(data.when and data.when != o_data.when) %}
|
||||||
|
DROP EVENT TRIGGER IF EXISTS {{ conn|qtIdent(o_data.name) }};
|
||||||
|
|
||||||
|
CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) if data.name else conn|qtIdent(o_data.name) }} ON {{ data.eventname if data.eventname else o_data.eventname }}
|
||||||
|
{% if data.when or o_data.when %}
|
||||||
|
WHEN TAG IN ({{ data.when if data.when else o_data.when }})
|
||||||
|
{% endif %}
|
||||||
|
EXECUTE PROCEDURE {{ data.eventfunname if data.eventfunname else o_data.eventfunname }}();
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% if data.name and data.name != o_data.name %}
|
||||||
|
ALTER EVENT TRIGGER {{ conn|qtIdent(o_data.name) }}
|
||||||
|
RENAME TO {{ conn|qtIdent(data.name) }};
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.comment and data.comment != o_data.comment %}
|
||||||
|
COMMENT ON EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
IS '{{ data.comment }}';
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.enabled and data.enabled != o_data.enabled %}
|
||||||
|
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
{% if data.enabled == "O" %}
|
||||||
|
ENABLE;
|
||||||
|
{% elif data.enabled == "D" %}
|
||||||
|
DISABLE;
|
||||||
|
{% elif data.enabled == "R" %}
|
||||||
|
ENABLE REPLICA;
|
||||||
|
{% elif data.enabled == "A" %}
|
||||||
|
ENABLE ALWAYS;
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.providers and
|
||||||
|
data.providers|length > 0
|
||||||
|
%}{% set seclabels = data.providers %}
|
||||||
|
{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
|
||||||
|
|
||||||
|
{% for r in seclabels.deleted %}
|
||||||
|
{{ SECLABLE.DROP(conn, 'EVENT TRIGGER', data.name, r.provider) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if 'added' in seclabels and seclabels.added|length > 0 %}
|
||||||
|
|
||||||
|
{% for r in seclabels.added %}
|
||||||
|
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
|
||||||
|
|
||||||
|
{% for r in seclabels.changed %}
|
||||||
|
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.eventowner and data.eventowner != o_data.eventowner %}
|
||||||
|
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
|
||||||
|
OWNER TO {{data.eventowner}};
|
||||||
|
{% endif %}
|
Loading…
Reference in New Issue
Block a user