要用PL/Tcl语言创建一个函数,使用已知的语法
CREATE FUNCTION funcname (argument-types) RETURNS return-type AS '
# PL/Tcl function body
' LANGUAGE 'pltcl';
PL/TclU是一样的,除了语言应该声明为 pltclu 之外.
函数体就是一段 Tcl 代码. 当在一个查询里面调用这个函数, 参数是作为变量 $1 ... $n 传递给 Tcl 脚本的. 结果是用通常的方法从 Tcl 代码中返回的,用一个 return 语句.比如, 一个简单的返回两个整数值的最大值函数可以这样定义:
CREATE FUNCTION tcl_max (integer, integer) RETURNS integer AS ' if {$1 > $2} {return $1} return $2 ' LANGUAGE 'pltcl' WITH (isStrict);
请注意子句 WITH (isStrict),它让我们可以不用考虑 输入为 NULL 的情况∶如果传递了一个 NULL,该函数实际上就不会被调用, 而只是自动返回一个 NULL 结果.
如果是一个不严格的函数,如果一个参数的实际数值是 NULL, 那么对应的 $n 变量将被设置为一个空字串. 要检测一个特定的参数是否为 NULL,可以使用函数 argisnull. 比如,假设我们要求tcl_max在一个参数为 null 而另外一个为非 null 时 返回非 null 参数,而不是 NULL∶
CREATE FUNCTION tcl_max (integer, integer) RETURNS integer AS ' if {[argisnull 1]} { if {[argisnull 2]} { return_null } return $2 } if {[argisnull 2]} { return $1 } if {$1 > $2} {return $1} return $2 ' LANGUAGE 'pltcl';
如上所述,要从 PL/Tcl 函数中返回一个 NULL 数值, 可以执行 return_null.不管函数是否 严格,我们都可以这么做.
复合类型的参数是当做 Tcl 数组传递给过程的. 数组中的元素名字就是复合类型里的属性名字. 如果在实际的行中的一个属性有 NULL 数值,那么它不会在数组中出现! 下面是一个在 PL/Tcl 中定义了 overpaid_2 函数(和我们以前老的 PostgreSQL 文档中的一样)的例子
CREATE FUNCTION overpaid_2 (EMP) RETURNS bool AS ' if {200000.0 < $1(salary)} { return "t" } if {$1(age) < 30 && 100000.0 < $1(salary)} { return "t" } return "f" ' LANGUAGE 'pltcl';
目前没有返回一个复合类型结果值的支持.
提供给 PL/Tcl 函数脚本的参数值都只是转换成文本形式 的输入参数(就象它们用 SELECT 语句显示出来的那样). 相反,return 可以用任何可以为函数所声明的返回类型 接受的输入格式的字串.因此,PL/Tcl 程序员可以把数据值当做文本操作.
有时候 在两个过程之间保存一些状态数据和非常有用的. 因为所有在一个后端运行的PL/Tcl过程共享同一个安全 Tcl 解释器. 所以实现这个目标相当容易. 因此,任何全局 Tcl 变量都是可以被所有PL/Tcl过程调用访问的, 并且将在该次 SQL 客户端联接过程中保持一致.(请注意PL/TclU函数 也类似地共享全局数据,但是它们在一个不同的 Tcl 解释器里并且无法和 PL/Tcl函数通讯.
为了保护 PL/Tcl 过程相互之间不至于互相干扰, 每个过程可以通过upvar命令访问一个全局数组. 此变量的全局名称是过程的内部名称,其局部名称是GD. 我们建议使用GD作为存储过程的私有状态数据的数组. 而把普通的 Tcl 全局变量只用于那些你想在多个过程之间共享的变量.
一个使用 GD 的例子在下面的 spi_execp 例子里显示.
在 PL/Tcl 过程体里有下面的命令可以用于从访问数据库∶
执行一个以字串形式给出的 SQL 查询.查询中的错误会导致抛出一个 错误.否则,该命令的返回值是查询处理的行数(选出,插入,更新,或者删除), 如果该查询是一个功能性语句则返回零.另外,如果查询是一条SELECT 语句,那么选出的字段按照下面描述的方法放在 Tcl 变量中.
可选的 -count 值告诉 spi_exec 在该查询中处理的最大的行数.其效果和把查询设置为一个游标, 然后说 FETCH n 是一样的.
如果查询是一个 SELECT 语句,那么 SELECT 的结果列的数值将放在 按照各字段命名的 Tcl 变量中.如果给出了 -array 选项, 那么字段值将放到这个命名的相关数组中,而 SELECT 的字段名可以用做 数组索引.
如果查询是 SELECT 语句并且没有给出 loop-body 脚本, 那么只有结果的头几行会存储到 Tcl 变量中;如果还有其他行的话, 将会被忽略.如果 SELECT 没有返回任何行,那么不会发生存储的现象 (这个情况可以通过检查 spi_exec 的结果来判断). 比如,
spi_exec "SELECT count(*) AS cnt FROM pg_proc"
将设置 Tcl 变量 $cnt 为系统表pg_proc中的行数.
如果给出了可选的 loop-body 参数, 那么它就是一小段 Tcl 脚本,它会为 SELECT 结果中的每一行执行一次 (注意∶如果给出的查询不是 SELECT,那么忽略 loop-body). 在每次迭代之前,当前行的字段的数值都存储到 Tcl 变量中去了.比如,
spi_exec -array C "SELECT * FROM pg_class" { elog DEBUG "have table $C(relname)" }
将为 pg_class 的每一行打印 DEBUG 日志信息.这个特性和其它 Tcl 循环构造的运做方式累死;特别是 continue 和 break 在循环体中的作用和平常是一样的.
如果一个 SELECT 结果的一个字段是 NULL,那么其目标变量就是 "unset" 而不是设置上什么东西.
为后面的执行准备并保存一个查询规划.保存的规划的生命期就是 当前后端的生命期.
查询可以使用 arguments,这些参数是规划实际 执行的时候提供的数值的占位符. 在查询字串里用符号 $1 ... $n 引用各个参数.如果查询使用了参数,那么参数类型名必需以一个 Tcl 列表 的形式给出.(如果没有使用参数,那么给 typelist 写一个空列表.)目前,参数类型必需和 pg_type 里显示的内部类型名一样; 比如是int4 而不是 integer.
spi_prepare 的返回值是一个可以在随后的 spi_execp 调用中使用的查询 ID.参阅 spi_execp 获取例子.
执行一个前面用 spi_prepare 准备的查询. queryid 是 spi_prepare 返回的 ID.如果该查询引用了参数,那么我们必需提供一个 value-list∶这是一个 Tcl 列表,里面包含 那些参数的实际数值.这个列表的长度必需和前面给 spi_prepare 提供的参数类型列表的长度一样长.如果查询 没有参数,那么省略 value-list.
-nulls 可选的数值是一个空白字串和字符 'n', 告诉 spi_execp 哪些参数是 NULL.如果给出, 那么它必需和 value-list 的长度相同. 如果没有给出,那么所有参数值都是非 NULL.
除了查询及其参数声明的方式之外,spi_execp 的使用方法 基本上和 spi_exec 一样.-count, -array,和 loop-body 选项 都是一样的,结果数值也一样.
下面是一个使用准备好的规划的 PL/Tcl 行数的例子∶
CREATE FUNCTION t1_count(integer, integer) RETURNS integer AS ' if {![ info exists GD(plan) ]} { # prepare the saved plan on the first call set GD(plan) [ spi_prepare \\ "SELECT count(*) AS cnt FROM t1 WHERE num >= \\$1 AND num <= \\$2" \\ [ list int4 int4 ] ] } spi_execp -count 1 $GD(plan) [ list $1 $2 ] return $cnt ' LANGUAGE 'pltcl';
请注意在函数里每个需要 Tcl 看到的反斜扛都必需写双份, 因为主分析器在 CREATE FUNCTION 里也处理反斜扛.我们需要在 给 spi_prepare 的查询字串里放反斜扛,以确保 $n 标记会原样传递给 spi_prepare, 而不是被 Tcl 的变量代换替换掉.
如果该查询是单行 INSERT, 返回最后的 spi_exec 或者 spi_execp 查询插入的行的 OID.(如果不是,你收到零.)
在给出的字串里将所由单引号和反斜扛字符复制成双份. 它可以用于安全地处理那些要输入到 spi_exec 或者 spi_prepare 中的 SQL 查询中的 引起字串.比如,假如一个查询看起来象这样
"SELECT '$val' AS ret"
这里的 Tcl 变量 val 实际上包含 doesn't. 这样最后的查询字串会是这样
SELECT 'doesn't' AS ret
而这个字串在 spi_exec 或 spi_prepare 的时候会导致一个分析错误. 提交的查询应该包含
SELECT 'doesn''t' AS ret
我们在 PL/Tcl 中可以这样构造
"SELECT '[ quote $val ]' AS ret"
spi_execp 的一个优点是你不需要象这样 引起参数值,因为参数决不会当做 SQL 查询字串的一部分分析.
发出一个日志或者错误消息.可能的级别是 DEBUG, LOG,INFO, NOTICE,WARNING, ERROR,和 FATAL. 大多数只是简单地发出指定消息, 就象后端的 C 函数 elog. ERROR 抛出一个错误条件∶该行数进一步的执行将中止, 同时退出当前事务.FATAL 退出当前事务并且导致当前后端 关闭(可能在 PL/Tcl 函数里没有什么理由使用这个错误级别,提供它主要 是为了完整).
触发器过程可以用 PL/Tcl 写.和 PostgreSQL 的传统一样,要当做触发器调用的过程必需声明为没有参数并且返回 类型为 trigger 的函数.
触发器管理器传递给过程体的信息是通过下面变量传递的:
CREATE TRIGGER 语句里的触发器名称.
导致触发器被调用的表的对象标识.
以一个空表元素为前导的表中字段名称的 Tcl 列表. 所以用Tcl命令 lsearch在列表里查找元素名称时, 返回的从 1 开始计数的正整数,与PostgreSQL 里字段编号的传统一样.
由触发器调用事件决定的字符串BEFORE或AFTER .
由触发器调用事件决定的字符串ROW或STATEMENT.
由触发器调用事件决定的字符串INSERT,UPDATE或 DELETE.
一个关联数组,包含 INSERT/UPDATE 动作的新表行,如果是 DELETE 则为空. 该数组是用字段名做索引的.那些为 NULL 的字段不会在数组中出现!
一个关联数组,包含 UPDATE/DELETE 动作的新表行,如果是 INSERT 则为空. 该数组是用字段名做索引的.那些为 NULL 的字段不会在数组中出现!
如同在 CREATE TRIGGER 语句里给出的参数一样的Tcl 参数表. 这些参数在过程体里可以通过$1...$n来访问.
触发器过程返回的值是字符串OK或SKIP之一, 或者一个象array get Tcl 命令返回的数组那样的东西. 如果返回值是OK,触发触发器的操作 (INSERT/UPDATE/DELETE)将会正常进行. SKIP告诉触发器管理器不声不响地忽略该行的操作. 如果返回一个数组,那么它告诉 PL/Tcl 返回一个修改后的 行给触发器管理器, 该行将代替在 $NEW (只在 INSERT/UPDATE 中起作用)中给出的行. 当然,这些只有在触发器是 BEFORE 和 FOR EACH ROW 时才有意义; 否则返回值将被忽略.
下面是一个小的触发器过程的例子, 它强制表内的一个整数值对行的更新次数进行跟踪. 对插入的新行,该值初始化为 0 并且在每次更新操作中加一:
CREATE FUNCTION trigfunc_modcount() RETURNS TRIGGER AS ' switch $TG_op { INSERT { set NEW($1) 0 } UPDATE { set NEW($1) $OLD($1) incr NEW($1) } default { return OK } } return [array get NEW] ' LANGUAGE 'pltcl'; CREATE TABLE mytab (num integer, description text, modcnt integer); CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
请注意触发器过程本身并不知道字段名字;那些是从触发器参数中 提供的.这样就让我们可以将触发器过程复用于不同的表.
PL/Tcl 使用时支持自动装载 Tcl 代码. 它识别一个特殊的表,pltcl_modules,该表被认为 包含 Tcl 代码的模块.如果存在这个表,则在创建完解释器后马上从该表中抓取 unknown 模块并装载到 Tcl 解释器中.
因为 unknown 模块实际上可以包含任何你需要的初始化脚本, 它通常是定义为一个 Tcl "unknown" 过程,在 Tcl 不能识别一个 调用的过程名的时候就调用它.PL/Tcl这个过程的标准版本试图在 pltcl_modules 里找到一个定义所需要过程的模块. 如果找到一个,那么把它装载入解释器,然后允许继续按照原来的过程调用 处理.另外还定义了一个表 pltcl_modfuncs,它提供了 哪个函数由哪个模块定义的索引,因此查找过程相当快.
PostgreSQL 包括维护这些表的 支持脚本∶ pltcl_loadmod,pltcl_listmod, pltcl_delmod,以及标准未知模块 share/unknown.pltcl 的源代码.这个模块可以一开始就 装载入每个数据库以便支持自动装载机制.
表 pltcl_modules 和 pltcl_modfuncs 必需 可以为所有人读取,但是把它做成只有数据库管理员可写并拥有是聪明的做法.
在 PostgreSQL 里,同一个函数名字 可以用于不同的函数,只要参数个数或者它们的类型不同. 不过,Tcl 要求所有的过程名字都是唯一的. PL/Tcl 通过把内部 Tcl 过程名字包含该过程的pg_proc行的对象 ID 来处理这些问题.因此同名不同参数类型的 PostgreSQL 行数也将会有不同的 Tcl 过程名. 这个问题通常对 PL/Tcl 程序员而言不算啥,但是在调试的时候可能会看到.