第十七章 使用触发器

2021-04-06  本文已影响0人  Cache技术分享

第十七章 使用触发器

本章介绍如何在Intersystems SQL中定义触发器。触发器是响应某些SQL事件执行的代码行。本章包括以下主题:

定义触发器

有几种方法可以为特定表定义触发器:

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
    // ... Class Property Definitions

    Trigger LogEvent [ Event = INSERT, Time = AFTER ]
    {
        // Trigger code to log an event 
    }
 }

必须拥有%create_trigger管理级别权限来创建触发器。必须具有删除触发器的%drop_trigger管理级别权限。

类的最大用户定义触发器数为200。

注意:Intersystems Iris不支持收集投影的表上的触发。用户无法定义这样的触发器,并且作为子表的集合的投影不认为涉及该基本集合的触发。
Intersystems Iris不支持修改Security.RolesSecurity.Users表的触发器。

触发器的类型

触发器由以下内容定义:

下面是可用的触发器及其等价的回调方法:

注意:当触发器执行时,它不能直接修改正在处理的表中的属性值。
这是因为InterSystems IRIS在字段(属性)值验证代码之后执行触发代码。
例如,触发器不能将LastModified字段设置为正在处理的行中的当前时间戳。
但是,触发器代码可以对表中的字段值发出更新。
更新执行自己的字段值验证。

AFTER Triggers

INSERTUPDATEDELETE事件发生后执行AFTER触发器:

递归触发器

触发器执行可以是递归的。
例如,如果表T1有一个对表T2执行插入操作的触发器,表T2也有一个对表T1执行插入操作的触发器。
当表T1有一个调用例程/过程的触发器,并且该例程/过程执行对T1的插入操作时,也可以发生递归。
触发器递归的处理取决于触发器的类型:

InterSystems IRIS不会阻止BEFORE语句触发器递归地执行。
在触发递归之前处理是程序员的责任。
如果BEFORE触发器代码不处理递归执行,可能会发生runtime <FRAMESTACK>错误。

Trigger Code

每个触发器包含执行触发操作的一行或多行代码。
每当与触发器关联的事件发生时,SQL引擎就会调用这段代码。
如果触发器是使用CREATE触发器定义的,则可以用ObjectScript或SQL编写此操作代码。
(InterSystems IRIS将SQL编写的代码转换为类定义中的ObjectScript。)
如果触发器是使用Studio定义的,那么这个操作代码必须用ObjectScript编写。

因为触发器的代码不是作为过程生成的,所以触发器中的所有局部变量都是公共变量。
这意味着触发器中的所有变量都应该用一个新语句显式声明;
这可以防止它们与调用触发器的代码中的变量发生冲突。

%ok, %msg, and %oper 系统变量

触发代码可以显式设置%ok=0
这会创建一个运行时错误,中止触发器的执行并回滚操作。
通常,在设置%ok=0之前,触发器代码显式地将%msg变量设置为用户指定的字符串,用于描述这个用户定义的触发器代码错误。

%ok变量是一个必须显式更新的公共变量。
在完成非触发代码SELECTINSERTUPDATEDELETE语句后,%ok的值与之前的值没有变化。
%ok仅在执行触发器代码时定义。

%msg:触发代码可以显式地将%msg变量设置为描述运行时错误原因的字符串。
设置变量%msg

%oper:仅在触发器代码中使用的变量。
触发器代码可以引用变量%oper,该变量包含触发触发器的事件(插入、更新或删除)的名称。

{fieldname}语法

在触发器代码中,可以使用特殊的{fieldname}语法引用字段值(对于属于触发器关联的表的字段)。
例如,下面是MyAppLogEvent触发器的定义。
Person类包含一个对ID字段的引用,如{ID}:

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
    // ... Definitions of other class members

    /// This trigger updates the LogTable after every insert
    Trigger LogEvent [ Event = INSERT, Time = AFTER ]
    {
        // get row id of inserted row
        NEW id,SQLCODE,%msg,%ok,%oper
        SET id = {ID}

        // INSERT value into Log table
        &sql(INSERT INTO LogTable 
            (TableName, IDValue) 
            VALUES ('MyApp.Person', :id))
        IF SQLCODE<0 {SET baderr="SQLCODE ERROR:"_SQLCODE_" "_%msg
                      SET %ok=0
                    RETURN baderr }

      }
   // ... Definitions of other class members

}

这个{fieldname}语法支持统一字段。
它不支持%SerialObject集合属性。
例如,如果表引用了嵌入的串行对象类Address(其中包含属性City),那么触发器语法{Address_City}就是对字段的有效引用。
触发器语法{Address}是对集合属性的引用,不能使用。

触发器代码中的宏

触发器代码可以包含一个引用字段名的宏定义(使用{fieldname}语法)。
但是,如果你的触发代码包含一个#Include预处理器指令,用于一个引用字段名的宏(使用{fieldname}语法),那么这个字段名就不能被访问。
这是因为InterSystems IRIS在代码被传递给宏预处理器之前,翻译触发器代码中的{fieldname}引用。
如果一个{fieldname}引用在#Include文件中,它不会在触发器代码中“看到”,因此不会被转换。

这种情况的解决方法是定义一个带参数的宏,然后将{fieldname}传递给触发器中的宏。
例如,#Include文件可以包含如下一行:

#Define dtThrowTrigger(%val) SET x=$GET(%val,"?")

然后在触发器中调用提供{fieldname}语法作为参数的宏:

  $$$dtThrowTrigger({%%ID})   

{name*O}{name*N}{name*C}触发代码语法

在更新触发器代码中有三种语法快捷方式可用。

可以使用下面的语法引用旧的(预更新的)值:

{fieldname*O}

其中fieldname是字段的名称,星号后面的字符是字母“O”(表示旧)。
对于插入触发器,{fieldname*O}总是空字符串("")

你可以使用下面的语法来引用新的(更新后的)值:

{fieldname*N}

其中fieldname是字段的名称,星号后面的字符是字母“N”(表示新字段)。
{fieldname*N}语法只能用于引用要存储的值;
它不能用来更改值。
不能在触发器代码中设置{fieldname*N}。
在插入或更新时计算字段的值应该通过其他方法实现,比如SqlComputeOnChange

可以使用以下语法测试字段值是否被更改(更新):

{fieldname*C}

其中,fieldname是字段的名称,星号后面的字符是字母“C”(表示已更改)。
{fieldname*C}的计算结果是1,如果字段已经被修改,0,如果它没有被修改。
对于插入触发器,InterSystems IRIS将{fieldname*C}设置为1。

对于具有流属性的类,如果SQL语句(INSERTUPDATE)没有插入/更新流属性本身,则对流属性{stream *N}{stream *O}的SQL触发器引用将返回流的OID
然而,如果SQL语句确实插入/更新了stream属性,{stream *O}仍然是OID,但{stream *N}的值被设置为以下之一:

如果一个流属性使用InterSystems IRIS对象更新,{stream *N}的值总是一个OID

注意:对于由串行对象的数组集合创建的子表触发器,触发器逻辑与对象访问/保存一起工作,但与SQL访问(插入或更新)不工作。

附加触发器代码语法

在ObjectScript中编写的触发器代码可以包含伪域引用变量{%%CLASSNAME}{%%CLASSNAMEQ}{%%OPERATION}{%%TABLENAME}{%%ID}
这些伪字段在类编译时被转换成特定的值。

可以从触发器代码、SQL计算代码和SQL映射定义中使用类方法,因为类方法不依赖于拥有开放对象。
必须使用##class(classname). methodname()语法从触发器代码中调用方法。
你不能使用..Methodname()语法,因为这个语法需要一个当前打开的对象。

可以将当前行字段的值作为类方法的参数传递,但是类方法本身不能使用字段语法。

Pulling Triggers

如果调用对应于该表的DML命令,则“拉出”(执行)已定义的触发器。

对于DML命令成功插入、更新或删除的每一行,都会拉取一行或行/对象触发器。

对于每个成功执行的INSERTUPDATEDELETE语句,都会拉出一次语句触发器,而不管该语句是否实际更改了表数据中的任何行。

快速插入不能用于具有插入触发器的表。

默认情况下,DDL语句和相应的触发操作被记录在日志中。
%NOJOURN关键字阻止DDL命令和触发动作的日志记录。

触发器和对象访问

如果触发器是用Foreach = row/object定义的,那么触发器也会在对象访问期间的特定点被调用,这取决于触发器定义的EventTime关键字,如下所示:

Event Time 此时也调用Trigger
INSERT BEFORE 在新对象的%Save()之前
INSERT AFTER 在新对象的%Save()
UPDATE BEFORE 在已存在对象的%Save()之前
UPDATE AFTER 在已存在对象的%Save()
DELETE BEFORE 在现有对象的%DeleteId()之前
DELETE AFTER 在现有对象的%DeleteId()

因此,也没有必要为了保持SQL和对象行为同步而实现回调方法,

在对象访问期间没有拔出触发器

默认情况下,SQL对象使用%Storage.Persistent存储。
InterSystems IRIS也支持 %Storage.SQL storage
SQL存储。

在使用 %Storage.SQL storage的类中保存或删除对象时。
SQL存储,所有语句(Foreach = statement)、行(Foreach = row)和行/对象(Foreach = row/object)触发器被拉出。
没有定义Foreach trigger关键字的触发器是行触发器。
提取所有触发器是默认行为。

但是,在使用%Storage.SQL storage保存或删除类中的对象时。
SQL,可以指定只有定义为Foreach = row/object的触发器应该被拉出。
定义为Foreach = statementForeach = row的触发器不会被拉取。
这是通过指定类参数OBJECTSPULLTRIGGERS = 0来实现的。
默认值是OBJECTSPULLTRIGGERS = 1

此参数仅应用于使用%Storage.SQL定义的类。

触发器与事务

触发器在事务中执行触发器码。它设置事务级别,然后执行触发器代码。成功完成触发器代码后,触发器提交事务。

注意:使用事务的触发器的结果是,如果触发器调用提交事务的代码,则触发器的完成失败,因为事务级别已经递减为0.调用生产的业务服务时可能发生这种情况。

使用INSERT语句级别对象触发器后,如果触发器集%OK = 0,则使用SQLCODE -131错误失败行的插入失败。如下所示,可能会发生交易回滚:

Auto_Commit模式是使用 SET TRANSACTION %COMMITMODE optionSetOption()方法建立的,如下所示 SET status=$SYSTEM.SQL.Util.SetOption("AutoCommit",intval,.oldval). 可用方法INTVAL值为0(无),1(隐式)和2(显式)。

触发器可以在触发器中的%MSG变量中设置错误消息。此消息将返回给呼叫者,给出触发器失败的信息。

列出触发器

在管理门户SQL接口目录详细信息中列出了为指定表定义的触发器。这列出了每个触发器的基本信息。

Information.schema.triggers类列出了当前命名空间中的定义触发器。对于每个触发信息.Schema.triggers列出了各种属性,包括触发器的名称,关联的架构和表名称,EventManipulation属性(插入,更新,删除,插入/更新,ActionTiming属性(之前,之后),创建的属性(触发创建时间戳)和ActionStatement属性,它是生成的SQL触发器代码。

创建的属性从上次修改课程定义时派生触发创建时间戳。因此,随后使用此类(例如,定义其他触发器)可能导致创建属性值的意外更新。

可以从SQL查询中访问此信息.Schema.triggers信息,如下例所示:

SELECT TABLE_NAME,TRIGGER_NAME,CREATED,EVENT_MANIPULATION,ACTION_TIMING,ACTION_ORIENTATION,ACTION_STATEMENT 
FROM INFORMATION_SCHEMA.TRIGGERS WHERE TABLE_SCHEMA='SQLUser'
image.png
上一篇下一篇

猜你喜欢

热点阅读