mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-01-23 23:13:38 -06:00
1) Ensure compound triggers should be displayed under Views. Fixes #4638.
2) Ensure Truncate option should be available for Compound Triggers. Fixes #4641.
This commit is contained in:
parent
624f229ce7
commit
0d6f07a035
@ -34,7 +34,8 @@ Use the fields in the *Events* tab to specify how and when the compound trigger
|
||||
|
||||
* Select the type of event(s) that will invoke the compound trigger; to select
|
||||
an event type, move the switch next to the event to the *YES* position.
|
||||
The supported event types are *INSERT*, *UPDATE*, *DELETE*.
|
||||
The supported event types are *INSERT*, *UPDATE*, *DELETE* and *TRUNCATE*.
|
||||
Views cannot have TRUNCATE triggers.
|
||||
* Use the *When* field to provide a boolean condition that will invoke the
|
||||
compound trigger.
|
||||
* If defining a column-specific compound trigger, use the *Columns* field to
|
||||
@ -46,10 +47,10 @@ Click the *Code* tab to continue.
|
||||
:alt: Compound Trigger dialog code tab
|
||||
:align: center
|
||||
|
||||
Use the *Code* field to specify the code for the four timing events
|
||||
*BEFORE STATEMENT*, *AFTER STATEMENT*, *BEFORE EACH ROW*, *AFTER EACH ROW*
|
||||
that will be invoked when the compound trigger fires. Basic template is provided
|
||||
with place holders.
|
||||
Use the *Code* field to specify the code for the five timing events
|
||||
*BEFORE STATEMENT*, *AFTER STATEMENT*, *BEFORE EACH ROW*, *AFTER EACH ROW*,
|
||||
*INSTEAD OF EACH ROW* that will be invoked when the compound trigger fires.
|
||||
Basic template is provided with place holders.
|
||||
|
||||
Click the *SQL* tab to continue.
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 60 KiB |
@ -52,4 +52,6 @@ Bug fixes
|
||||
| `Issue #4582 <https://redmine.postgresql.org/issues/4582>`_ - Fix console error when changing kind(SQL/BATCH) for pgAgent job step.
|
||||
| `Issue #4585 <https://redmine.postgresql.org/issues/4585>`_ - Fix double click issue to expand the contents of a cell if the resultset was not editable.
|
||||
| `Issue #4586 <https://redmine.postgresql.org/issues/4586>`_ - Fix generation of reverse engineered SQL for Rules.
|
||||
| `Issue #4635 <https://redmine.postgresql.org/issues/4635>`_ - Ensure compound triggers for event should be updated properly.
|
||||
| `Issue #4635 <https://redmine.postgresql.org/issues/4635>`_ - Ensure compound triggers for event should be updated properly.
|
||||
| `Issue #4638 <https://redmine.postgresql.org/issues/4638>`_ - Ensure compound triggers should be displayed under Views.
|
||||
| `Issue #4641 <https://redmine.postgresql.org/issues/4641>`_ - Ensure Truncate option should be available for Compound Triggers.
|
@ -185,7 +185,13 @@ define('pgadmin.node.compound_trigger', [
|
||||
type: 'int', disabled: true, mode: ['properties'],
|
||||
},{
|
||||
id: 'is_enable_trigger', label: gettext('Trigger enabled?'),
|
||||
type: 'switch', disabled: 'inSchema', mode: ['edit', 'properties'],
|
||||
type: 'switch', mode: ['edit', 'properties'],
|
||||
disabled: function() {
|
||||
if(this.node_info && ('catalog' in this.node_info || 'view' in this.node_info)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', mode: ['create','edit', 'properties'],
|
||||
label: gettext('FOR Events'), group: gettext('Events'), contentClass: 'row',
|
||||
@ -228,6 +234,23 @@ define('pgadmin.node.compound_trigger', [
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'evnt_truncate', label: gettext('TRUNCATE'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
group: gettext('FOR Events'),
|
||||
extraToggleClasses: 'pg-el-sm-6',
|
||||
controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
|
||||
disabled: function(m) {
|
||||
var evn_truncate = m.get('evnt_truncate');
|
||||
// Views cannot have TRUNCATE triggers.
|
||||
if ('view' in m.node_info)
|
||||
return true;
|
||||
|
||||
if (!_.isUndefined(evn_truncate) && m.node_info['server']['server_type'] == 'ppas')
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
}],
|
||||
},{
|
||||
id: 'whenclause', label: gettext('When'),
|
||||
@ -337,6 +360,12 @@ define('pgadmin.node.compound_trigger', [
|
||||
' -- Enter any local declarations here\n' +
|
||||
'BEGIN\n' +
|
||||
' -- Enter any required code here\n' +
|
||||
'END;\n\n' +
|
||||
'-- INSTEAD OF EACH ROW block. Delete if not required.\n' +
|
||||
'INSTEAD OF EACH ROW IS\n' +
|
||||
' -- Enter any local declarations here\n' +
|
||||
'BEGIN\n' +
|
||||
' -- Enter any required code here\n' +
|
||||
'END;');
|
||||
},
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
@ -397,11 +426,21 @@ define('pgadmin.node.compound_trigger', [
|
||||
},
|
||||
// Check to whether trigger is disable ?
|
||||
canCreate_with_compound_trigger_enable: function(itemData, item, data) {
|
||||
var treeData = this.getTreeNodeHierarchy(item);
|
||||
if ('view' in treeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemData.icon === 'icon-compound_trigger-bad' &&
|
||||
this.canCreate.apply(this, [itemData, item, data]);
|
||||
},
|
||||
// Check to whether trigger is enable ?
|
||||
canCreate_with_compound_trigger_disable: function(itemData, item, data) {
|
||||
var treeData = this.getTreeNodeHierarchy(item);
|
||||
if ('view' in treeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemData.icon === 'icon-compound_trigger' &&
|
||||
this.canCreate.apply(this, [itemData, item, data]);
|
||||
},
|
||||
|
@ -0,0 +1,16 @@
|
||||
-- Compound Trigger: test_compound_trigger_$%{}[]()&*^!@"'`\/#
|
||||
|
||||
-- DROP TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger;
|
||||
|
||||
CREATE OR REPLACE TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#"
|
||||
FOR INSERT OR DELETE OR TRUNCATE OR UPDATE
|
||||
ON testschema.table_for_compound_trigger
|
||||
COMPOUND TRIGGER
|
||||
var character varying(20) DEFAULT 'Global_var';
|
||||
|
||||
BEFORE STATEMENT IS
|
||||
BEGIN
|
||||
DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var);
|
||||
var := 'BEFORE STATEMENT';
|
||||
END;
|
||||
END "test_compound_trigger_$%{}[]()&*^!@""'`\/#";
|
@ -94,6 +94,27 @@
|
||||
"data": {
|
||||
"name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#"
|
||||
}
|
||||
}, {
|
||||
"type": "create",
|
||||
"name": "Create compound trigger for all event",
|
||||
"endpoint": "NODE-compound_trigger.obj",
|
||||
"sql_endpoint": "NODE-compound_trigger.sql_id",
|
||||
"data": {
|
||||
"name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#",
|
||||
"prosrc": "var varchar2(20) := 'Global_var';\n\nBEFORE STATEMENT IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('Before Statement: ' || var);\n\tvar := 'BEFORE STATEMENT';\nEND;",
|
||||
"evnt_insert": true,
|
||||
"evnt_update": true,
|
||||
"evnt_delete": true,
|
||||
"evnt_truncate": true
|
||||
},
|
||||
"expected_sql_file": "create_for_all_event.sql"
|
||||
}, {
|
||||
"type": "delete",
|
||||
"name": "Drop Compound Trigger",
|
||||
"endpoint": "NODE-compound_trigger.delete_id",
|
||||
"data": {
|
||||
"name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -20,12 +20,28 @@ from pgadmin.browser.server_groups.servers.databases.tests import utils as \
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression import parent_node_dict
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.views.tests \
|
||||
import utils as view_utils
|
||||
|
||||
|
||||
class CompoundTriggersAddTestCase(BaseTestGenerator):
|
||||
"""This class will add new compound trigger under table node."""
|
||||
skip_on_database = ['gpdb']
|
||||
scenarios = [
|
||||
('Create compound trigger for all events',
|
||||
dict(
|
||||
url='/browser/compound_trigger/obj/',
|
||||
data={
|
||||
"prosrc": "var varchar2(20) := 'Global_var';\n\n"
|
||||
"BEFORE STATEMENT IS\nBEGIN\n "
|
||||
"DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)"
|
||||
";\n var := 'BEFORE STATEMENT';\nEND;",
|
||||
"evnt_insert": True,
|
||||
"evnt_update": True,
|
||||
"evnt_delete": True,
|
||||
"evnt_truncate": True
|
||||
}
|
||||
)),
|
||||
('Create compound trigger for insert and delete',
|
||||
dict(
|
||||
url='/browser/compound_trigger/obj/',
|
||||
@ -75,6 +91,47 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
|
||||
"columns": ["id", "name"]
|
||||
}
|
||||
)),
|
||||
('Create compound trigger for truncate',
|
||||
dict(
|
||||
url='/browser/compound_trigger/obj/',
|
||||
data={
|
||||
"prosrc": "var varchar2(20) := 'Global_var';\n\n"
|
||||
"BEFORE STATEMENT IS\nBEGIN\n "
|
||||
"DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)"
|
||||
";\n var := 'BEFORE STATEMENT';\nEND;",
|
||||
"evnt_truncate": True
|
||||
}
|
||||
)),
|
||||
('Create compound trigger for insert delete and update on view',
|
||||
dict(
|
||||
url='/browser/compound_trigger/obj/',
|
||||
data={
|
||||
"prosrc": "var varchar2(20) := 'Global_var';\n\n"
|
||||
"BEFORE STATEMENT IS\nBEGIN\n "
|
||||
"DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)"
|
||||
";\n var := 'BEFORE STATEMENT';\nEND;",
|
||||
"evnt_insert": True,
|
||||
"evnt_update": True,
|
||||
"evnt_delete": True,
|
||||
"evnt_truncate": False
|
||||
},
|
||||
on_view=True
|
||||
)),
|
||||
('Create compound trigger for instead of each row',
|
||||
dict(
|
||||
url='/browser/compound_trigger/obj/',
|
||||
data={
|
||||
"prosrc": "var varchar2(20) := 'Global_var';\n\n"
|
||||
"INSTEAD OF EACH ROW IS\nBEGIN\n "
|
||||
"DBMS_OUTPUT.PUT_LINE('Instead of: ' || var)"
|
||||
";\n var := 'INSTEAD OF EACH ROW';\nEND;",
|
||||
"evnt_insert": True,
|
||||
"evnt_update": True,
|
||||
"evnt_delete": True,
|
||||
"evnt_truncate": False
|
||||
},
|
||||
on_view=True
|
||||
)),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
@ -112,6 +169,14 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
|
||||
self.table_id = tables_utils.create_table(self.server, self.db_name,
|
||||
self.schema_name,
|
||||
self.table_name)
|
||||
view_sql = "CREATE OR REPLACE VIEW %s.%s AS SELECT 'Hello World'; " \
|
||||
"ALTER TABLE %s.%s OWNER TO %s"
|
||||
self.view_name = \
|
||||
"view_compound_trigger_%s" % (str(uuid.uuid4())[1:8])
|
||||
self.view_id = view_utils.create_view(self.server, self.db_name,
|
||||
self.schema_name,
|
||||
view_sql,
|
||||
self.view_name)
|
||||
|
||||
def runTest(self):
|
||||
"""This function will create compound trigger under table node."""
|
||||
@ -120,10 +185,14 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
|
||||
|
||||
self.data.update({"name": trigger_name})
|
||||
|
||||
object_id = self.table_id
|
||||
if hasattr(self, 'on_view'):
|
||||
object_id = self.view_id
|
||||
|
||||
response = self.tester.post(
|
||||
"{0}{1}/{2}/{3}/{4}/{5}/".format(self.url, utils.SERVER_GROUP,
|
||||
self.server_id, self.db_id,
|
||||
self.schema_id, self.table_id),
|
||||
self.schema_id, object_id),
|
||||
data=json.dumps(self.data),
|
||||
content_type='html/json'
|
||||
)
|
||||
|
@ -9,7 +9,10 @@ CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }}
|
||||
FOR {% if data.evnt_insert is not defined %}{% if o_data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% else %}{% if data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% endif %}{% if data.evnt_delete is not defined %}{% if o_data.evnt_delete %}
|
||||
{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}
|
||||
{% endif %}{% else %}{% if data.evnt_delete %}
|
||||
{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %}
|
||||
{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_truncate is not defined %}{% if o_data.evnt_truncate %}
|
||||
{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %}
|
||||
{% endif %}{% else %}{% if data.evnt_truncate %}
|
||||
{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %}
|
||||
{% if or_flag %} OR {% endif %}UPDATE{% if o_data.columns|length > 0 %} OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}
|
||||
{% endif %}{% else %}{% if data.evnt_update %}
|
||||
{% if or_flag %} OR {% endif %}UPDATE{% if o_data.columns|length > 0 %} OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}{% endif %}
|
||||
|
@ -187,8 +187,13 @@ define('pgadmin.node.trigger', [
|
||||
type: 'int', disabled: true, mode: ['properties'],
|
||||
},{
|
||||
id: 'is_enable_trigger', label: gettext('Trigger enabled?'),
|
||||
type: 'switch', disabled: 'inSchema', mode: ['edit', 'properties'],
|
||||
group: gettext('Definition'),
|
||||
type: 'switch', mode: ['edit', 'properties'], group: gettext('Definition'),
|
||||
disabled: function() {
|
||||
if(this.node_info && ('catalog' in this.node_info || 'view' in this.node_info)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'is_row_trigger', label: gettext('Row trigger?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
@ -629,11 +634,21 @@ define('pgadmin.node.trigger', [
|
||||
canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
// Check to whether trigger is disable ?
|
||||
canCreate_with_trigger_enable: function(itemData, item, data) {
|
||||
var treeData = this.getTreeNodeHierarchy(item);
|
||||
if ('view' in treeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemData.icon === 'icon-trigger-bad' &&
|
||||
this.canCreate.apply(this, [itemData, item, data]);
|
||||
},
|
||||
// Check to whether trigger is enable ?
|
||||
canCreate_with_trigger_disable: function(itemData, item, data) {
|
||||
var treeData = this.getTreeNodeHierarchy(item);
|
||||
if ('view' in treeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemData.icon === 'icon-trigger' &&
|
||||
this.canCreate.apply(this, [itemData, item, data]);
|
||||
},
|
||||
|
@ -874,6 +874,83 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
SQL_data += SQL
|
||||
return SQL_data
|
||||
|
||||
def get_compound_trigger_sql(self, vid):
|
||||
"""
|
||||
Get all compound trigger nodes associated with view node,
|
||||
generate their sql and render into sql tab
|
||||
"""
|
||||
if self.manager.server_type == 'ppas' \
|
||||
and self.manager.version >= 120000:
|
||||
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.utils\
|
||||
import trigger_definition
|
||||
|
||||
# Define template path
|
||||
self.ct_trigger_temp_path = 'compound_triggers'
|
||||
|
||||
SQL_data = ''
|
||||
SQL = render_template("/".join(
|
||||
[self.ct_trigger_temp_path,
|
||||
'sql/{0}/#{1}#/nodes.sql'.format(
|
||||
self.manager.server_type, self.manager.version)]),
|
||||
tid=vid)
|
||||
|
||||
status, data = self.conn.execute_dict(SQL)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=data)
|
||||
|
||||
for trigger in data['rows']:
|
||||
SQL = render_template("/".join(
|
||||
[self.ct_trigger_temp_path,
|
||||
'sql/{0}/#{1}#/properties.sql'.format(
|
||||
self.manager.server_type, self.manager.version)]),
|
||||
tid=vid,
|
||||
trid=trigger['oid']
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_dict(SQL)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
continue
|
||||
res_rows = dict(res['rows'][0])
|
||||
res_rows['table'] = res_rows['relname']
|
||||
res_rows['schema'] = self.view_schema
|
||||
|
||||
if len(res_rows['tgattr']) > 1:
|
||||
columns = ', '.join(res_rows['tgattr'].split(' '))
|
||||
SQL = render_template("/".join(
|
||||
[self.ct_trigger_temp_path,
|
||||
'sql/{0}/#{1}#/get_columns.sql'.format(
|
||||
self.manager.server_type,
|
||||
self.manager.version)]),
|
||||
tid=trigger['oid'],
|
||||
clist=columns)
|
||||
|
||||
status, rset = self.conn.execute_2darray(SQL)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
# 'tgattr' contains list of columns from table
|
||||
# used in trigger
|
||||
columns = []
|
||||
|
||||
for col_row in rset['rows']:
|
||||
columns.append(col_row['name'])
|
||||
|
||||
res_rows['columns'] = columns
|
||||
|
||||
res_rows = trigger_definition(res_rows)
|
||||
SQL = render_template("/".join(
|
||||
[self.ct_trigger_temp_path,
|
||||
'sql/{0}/#{1}#/create.sql'.format(
|
||||
self.manager.server_type, self.manager.version)]),
|
||||
data=res_rows, display_comments=True)
|
||||
SQL_data += '\n'
|
||||
SQL_data += SQL
|
||||
|
||||
return SQL_data
|
||||
|
||||
def get_trigger_sql(self, vid):
|
||||
"""
|
||||
Get all trigger nodes associated with view node,
|
||||
@ -943,7 +1020,8 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
if not status:
|
||||
return internal_server_error(errormsg=result)
|
||||
|
||||
# Update the trigger function which we have fetched with schemaname
|
||||
# Update the trigger function which we have fetched with
|
||||
# schemaname
|
||||
if (
|
||||
'rows' in result and len(result['rows']) > 0 and
|
||||
'tfunctions' in result['rows'][0]
|
||||
@ -1083,6 +1161,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
SQL_data += SQL
|
||||
SQL_data += self.get_rule_sql(vid)
|
||||
SQL_data += self.get_trigger_sql(vid)
|
||||
SQL_data += self.get_compound_trigger_sql(vid)
|
||||
SQL_data += self.get_index_sql(did, vid)
|
||||
|
||||
return ajax_response(response=SQL_data)
|
||||
|
@ -13,3 +13,5 @@ from pgadmin.browser.server_groups.servers.databases.schemas.tables.triggers \
|
||||
import blueprint as triggers_modules
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.tables.rules \
|
||||
import blueprint as rules_modules
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.tables.\
|
||||
compound_triggers import blueprint as compound_trigger_modules
|
||||
|
Loading…
Reference in New Issue
Block a user