【openGauss】如何通过pg_trigger.tgtype获取触发器的各种触发条件

前言

最近看到反馈兼容的dba_triggers视图中,同一个触发器的trigger_event被拆成了多行,和ORACLE中表现不一致,于是我进行了一些分析,发现是在其引用的information_schema.triggers视图中就已经拆开成了INSERT/DELETE/UPDATE,但是这些属性都是通过tgtype这一个int2整型的字段获取的,甚至连before/after/instead of/row/statement 等都是通过这一个字段。一个值存多种信息,这在ORACLE的数据字典视图里很常见,无非就是按二进制位来判断,于是我尝试自己猜一猜,看能不能从这个数字中识别出规律。

测试和分析

先建一堆测试触发器

sql 复制代码
create schema test1;
create schema test2;

CREATE TABLE test1.test_trigger_src_tbl(id1 INT, id2 INT, id3 INT);

CREATE OR REPLACE FUNCTION test1.tri_test_func() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--before insert/update row
CREATE TRIGGER test_trigger
           BEFORE insert or update ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func();
           
CREATE TABLE test2.test_trigger_src_tbl(id1 INT, id2 INT, id3 INT);

CREATE OR REPLACE FUNCTION test2.tri_test_func() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;
           
--不同schema下的同名触发器
CREATE TRIGGER test_trigger
           BEFORE insert or update ON test2.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test2.tri_test_func();
           
CREATE OR REPLACE FUNCTION test1.tri_test_func1() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

-- before insert row
CREATE TRIGGER test_trigger1
           BEFORE insert  ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func1();

CREATE OR REPLACE FUNCTION test1.tri_test_func2() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--before insert/delete row
CREATE TRIGGER test_trigger2
           BEFORE insert or DELETE ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func2();
          
CREATE OR REPLACE FUNCTION test1.tri_test_func3() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--after insert/delete/update row
CREATE TRIGGER test_trigger3
           AFTER insert or delete or UPDATE ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func3();
          
CREATE OR REPLACE FUNCTION test1.tri_test_func4() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;
           
create view test1.test_trigger_src_tbl_V as select * from test1.test_trigger_src_tbl;

--instead delete row
CREATE TRIGGER test_trigger4
           instead OF DELETE ON test1.test_trigger_src_tbl_V
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func4();

CREATE OR REPLACE FUNCTION test1.tri_test_func5() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--before truncate statement
CREATE TRIGGER test_trigger5
           before truncate ON test1.test_trigger_src_tbl
           FOR EACH STATEMENT 
           EXECUTE PROCEDURE test1.tri_test_func5();
           
          
CREATE OR REPLACE FUNCTION test1.tri_test_func6() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--after delete row
CREATE TRIGGER test_trigger6
           AFTER delete  ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func6();
           
CREATE OR REPLACE FUNCTION test1.tri_test_func7() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--after truncate statement
CREATE TRIGGER test_trigger7
           AFTER truncate ON test1.test_trigger_src_tbl
           FOR EACH STATEMENT 
           EXECUTE PROCEDURE test1.tri_test_func7();

 CREATE OR REPLACE FUNCTION test1.tri_test_func8() RETURNS TRIGGER AS
           $$
           DECLARE
           BEGIN
                   INSERT INTO test_trigger_des_tbl VALUES(NEW.id1, NEW.id2, NEW.id3);
                   RETURN NEW;
           END
           $$ LANGUAGE PLPGSQL;

--after update/delete row
CREATE TRIGGER test_trigger8
           AFTER update or delete  ON test1.test_trigger_src_tbl
           FOR EACH ROW
           EXECUTE PROCEDURE test1.tri_test_func8();

然后查询pg_trigger表,并将tgtype转换成二进制数值显示(注意这里int2无法直接转换成bit类型)

sql 复制代码
select tgname,tgtype,tgtype::int4::bit(8) from pg_trigger;
tgname tgtype tgtype 实际类型
test_trigger 23 00010111 before insert update row
test_trigger 23 00010111 before insert update
test_trigger1 7 00000111 before insert row
test_trigger2 15 00001111 before insert/delete row
test_trigger3 29 00011101 after insert/delete/update row
test_trigger4 73 01001001 instead delete row
test_trigger5 34 00100010 before truncate statement
test_trigger6 9 00001001 after delete row
test_trigger7 32 00100000 after truncate statement
test_trigger8 25 00011001 after update/delete row

得到这个信息,可以看出规律。我们在表格中转置一下看看

tgname test_trigger test_trigger test_trigger1 test_trigger2 test_trigger3 test_trigger4 test_trigger5 test_trigger6 test_trigger7 test_trigger8
tgtype 23 23 7 15 29 73 34 9 32 25
7 0 0 0 0 0 0 0 0 0 0
6 0 0 0 0 0 1 0 0 0 0
5 0 0 0 0 0 0 1 0 1 0
4 1 1 0 0 1 0 0 0 0 1
3 0 0 0 1 1 1 0 1 0 1
2 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 0 0 1 0 0 0
0 1 1 1 1 1 1 0 1 0 1
实际类型 before insert update row before insert update before insert row before insert/delete row after insert/delete/update row instead delete row before truncate statement after delete row after truncate statement after update/delete row

然后很容易就能对比得到每个二进制位所表示的含义

第几位 含义
7 无用位
6 是否insead of
5 是否truncate
4 是否update
3 是否delete
2 是否insert
1 是否before(不是before就是after)
0 是否row(不是row就是statement)

以上都是纯用SQL查询比较猜出来的,虽然过程也比较简单,但还是有点费时间。但实际上,如果能看懂C语言源码,一眼就能知道应该怎么去解析tgtype
openGauss-server\src\include\catalog\pg_trigger.hhttps://gitee.com/opengauss/openGauss-server/blob/master/src/include/catalog/pg_trigger.h

复制代码
sql 复制代码
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW                (1 << 0)
#define TRIGGER_TYPE_BEFORE             (1 << 1)
#define TRIGGER_TYPE_INSERT             (1 << 2)
#define TRIGGER_TYPE_DELETE             (1 << 3)
#define TRIGGER_TYPE_UPDATE             (1 << 4)
#define TRIGGER_TYPE_TRUNCATE           (1 << 5)
#define TRIGGER_TYPE_INSTEAD            (1 << 6)

另外,为什么明明有INSERT/DELETE/UPDATE/TRUNCATE四种,但information_schema.triggers里为什么没有truncate的呢?其实在视图里,明确有写

sql 复制代码
         -- hard-wired refs to TRIGGER_TYPE_INSERT, TRIGGER_TYPE_DELETE,
         -- TRIGGER_TYPE_UPDATE; we intentionally omit TRIGGER_TYPE_TRUNCATE
         (VALUES (4, 'INSERT'),
                 (8, 'DELETE'),
                 (16, 'UPDATE')) AS em (num, text)

我们故意省略TRIGGER_TYPE_TRUNCATE

知道以上规则后,我们可以尝试自己写一个dba_trigger视图,这里对event提供两种写法

sql 复制代码
case when tgtype&32<>0 then 'TRUNCATE'
     when tgtype&4<>0 and tgtype&16<>0 and tgtype&8<>0 then 'INSERT OR UPDATE OR DELETE'
     when tgtype&4<>0 and tgtype&16<>0 and tgtype&8=0 then 'INSERT OR UPDATE'
     when tgtype&4<>0 and tgtype&16=0 and tgtype&8=0 then 'INSERT'
     when tgtype&4=0 and tgtype&16<>0 and tgtype&8<>0 then 'UPDATE OR DELETE'
     when tgtype&4=0 and tgtype&16<>0 and tgtype&8=0 then 'UPDATE'
     when tgtype&4=0 and tgtype&16=0 and tgtype&8<>0 then 'DELETE'
     when tgtype&4<>0 and tgtype&16=0 and tgtype&8<>0 then 'INSERT OR DELETE'
end
sql 复制代码
case substring(tgtype::int4::bit(8) from 3 for 4)
      when B'0111' then 'INSERT OR UPDATE OR DELETE'
      when B'0101' then 'INSERT OR UPDATE'
      when B'0001' then 'INSERT'
      when B'0110' then 'UPDATE OR DELETE'
      when B'0010' then 'DELETE'
      when B'0011' then 'INSERT OR DELETE'
      when B'1000' then 'TRUNCATE'
end

视图代码地址:

https://gitee.com/enmotech/cmpat-tools/blob/master/Oracle_Views.sql

结尾

开头有提到,在ORACLE中也经常这样处理属性值,比如以下就是一个oracle的user_triggers视图的一段节选,用这一个property字段表示了很多种属性

sql 复制代码
decode(bitand(t.property, 8192),
       8192, decode(bitand(t.property, 131072),
                    131072, 'REVERSE', 'FORWARD'), 'NO'),
decode(bitand(t.property, 16384),
             16384, 'YES', 'NO'),
decode(bitand(t.property, 32768),
             32768, 'YES', 'NO'),

好的设计都是相通的,虽然这种方式不利于直接用SQL从数据字典基表中查询明确的属性值,但是能节省很多存储空间,并且在内存中直接判断二进制会更快比字符串更快。

相关推荐
剩下了什么3 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥4 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉4 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变4 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
山岚的运维笔记6 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里7 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科7 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦7 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
晚霞的不甘8 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
市场部需要一个软件开发岗位9 小时前
JAVA开发常见安全问题:纵向越权
java·数据库·安全