pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
在 Postgres 中,只要参数个数或参数类型 不同,就可以复用同一个函数名。这会与 Tcl 过程名冲突。为了在 PL/Tcl 中提供同样的灵活性,内部 Tcl 过程名中包含该过程的 pg_proc 行的对象 ID 作为其名称的一部分。因此,同一个 Postgres 函数的不同参数类型版本对 Tcl 来说也是不同的。
要用 PL/Tcl 语言创建函数,可使用标准语法
CREATE FUNCTIONfuncname(argument-types) RETURNSreturn-typeAS ' # PL/Tcl function body ' LANGUAGE 'pltcl';
调用函数时,参数以变量 $1 ... $n 的形式提供给 Tcl 过程体。结果由 Tcl 代码按通常方式用 return 语句返回。例如,一个返回两个 int4 值中较大值的函数可以定义为:
CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
if {$1 > $2} {return $1}
return $2
' LANGUAGE 'pltcl';
要从 PL/Tcl 函数返回 NULL 值,请执行 return_null。
复合类型参数会作为 Tcl 数组提供给过程。 数组中的元素名就是该复合类型的属性名。如果实际行中的某个属性为 NULL,它就不会出现在数组中!下面是一个在 PL/Tcl 中定义 overpaid_2 函数(见旧版 Postgres 文档)的例子
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';
有时(尤其是在使用稍后描述的 SPI 函数时)需要在两次过程调用之间保留某些全局状态数据。这很容易做到,因为同一后端中执行的所有 PL/Tcl 过程共享同一个安全的 Tcl 解释器。
为帮助防止 PL/Tcl 过程产生不希望的副作用, 每个过程都可通过 upvar 命令访问一个数组。该变量的全局名称是过程的内部 名称,局部名称是 GD。建议把 GD 用于保存过程的私有状态数据。只有当你明确希望某些值在多个过程之间共享时,才应使用常规的 Tcl 全局变量。
在 Postgres 中,触发器过程定义为无参数、返回类型为 opaque 的函数。在 PL/Tcl 语言中也是如此。
来自触发器管理器的信息通过下列变量提供给过程体:
$TG_nameCREATE TRIGGER 语句中触发器的名称。
$TG_relid导致触发器过程被调用的表的对象 ID。
$TG_relatts表字段名构成的 Tcl 列表,前面带有一个空列表元素。 因此,用 Tcl 的 lsearch 命令在列表中查找元素名时, 返回的正编号与 pg_attribute 系统目录中字段的编号 一样从 1 开始。
$TG_when字符串 BEFORE 或 AFTER,取决于触发器调用的事件。
$TG_level字符串 ROW 或 STATEMENT,取决于触发器调用的事件。
$TG_op字符串 INSERT、UPDATE 或 DELETE,取决于触发器调用的事件。
$NEW一个包含新表行值的数组,用于 INSERT/UPDATE 动作;对于 DELETE 则为空。
$OLD一个包含旧表行值的数组,用于 UPDATE/DELETE 动作;对于 INSERT 则为空。
$GD上文所述的全局状态数据数组。
$args一个 Tcl 列表,包含 CREATE TRIGGER 语句中给出的过程参数。这些参数也可以在过程体中以 $1 ... $n 的形式访问。
触发器过程的返回值是字符串 OK 或 SKIP 之一,或者是由 'array get' Tcl 命令返回的列表。如果返回值为 OK,触发该触发器的正常操作(INSERT/UPDATE/DELETE) 将会发生。显然,SKIP 告诉触发器管理器静默地取消该操作。'array get' 返回的列表告诉 PL/Tcl 向触发器管理器返回一个修改后的行,用该行代替 $NEW 中给出的行被插入(仅限 INSERT/UPDATE)。不用说,所有这些只在触发器是 BEFORE 且为 FOR EACH ROW 时才有意义。
下面是一个简单的触发器过程示例,它强制用表中的一个整数值记录 对该行执行的更新次数。对于新插入的行,该值被初始化为 0,之后 每次更新操作都将其递增:
CREATE FUNCTION trigfunc_modcount() RETURNS OPAQUE 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 int4, modcnt int4, description text);
CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
在 PL/Tcl 过程体中可以使用下列命令来访问数据库:
level msg发出一条日志消息。可用级别有 NOTICE、ERROR、 FATAL、DEBUG 和 NOIND,与 elog C 函数相同。
string将字符串中所有出现的单引号和反斜杠字符都加倍。 在给 spi_exec 或 spi_prepare 的查询字符串中使用变量时 应使用它(spi_execp 的值列表不需要)。 设想类似下面的查询字符串
"SELECT '$val' AS ret"
其中 Tcl 变量 val 实际包含 "doesn't"。这将得到 最终的查询字符串
"SELECT 'doesn't' AS ret"
这会在 spi_exec 或 spi_prepare 期间导致解析错误。 它应当包含
"SELECT 'doesn''t' AS ret"
因此必须写成
"SELECT '[ quote $val ]' AS ret"
n? ?-array name? query ?loop-body?为查询调用解析器/规划器/优化器/执行器。 可选的 -count 值告诉 spi_exec 该查询最多处理的行数。
如果查询是 SELECT 语句且给出了可选的 loop-body(一段像 foreach 语句中那样的 Tcl 命令体),则对选出的每一行 求值一次,并且对 continue/break 的表现符合预期。所选 字段的值被放入以列名命名的变量中。因此
spi_exec "SELECT count(*) AS cnt FROM pg_proc"
会把变量 $cnt 设置为 pg_proc 系统目录中的 行数。如果给出了选项 -array,列值将存储在以列名为索引、名为 'name' 的关联数组中,而不是各个单独的变量中。
spi_exec -array C "SELECT * FROM pg_class" {
elog DEBUG "have table $C(relname)"
}
会为 pg_class 的每一行打印一条 DEBUG 日志消息。spi_exec 的返回值是 查询所影响的行数,如全局变量 SPI_processed 中所示。
query typelist预备并保存一个查询计划供后续执行。它与 C 层的 SPI_prepare 有一点不同:该计划会被自动复制到顶层 内存上下文。因此,目前没有办法预备一个 计划而不保存它。
如果查询引用了参数,则必须以 Tcl 列表的形式给出类型名。spi_prepare 的返回值是一个查询 ID,供后续调用 spi_execp 时使用。示例见 spi_execp。
n? ?-arrayname? ?-nullsstring? queryid ?value-list? ?loop-body?带变量替换地执行一个来自 spi_prepare 的已预备计划。 可选的 -count 值告诉 spi_execp 该查询最多处理的 行数。
-nulls 的可选值是一个由空格和 'n' 字符组成的字符串, 告诉 spi_execp 哪些值是 NULL。如果给出,它的长度必须与值的个数完全 相同。
queryid 就是 spi_prepare 调用返回的 ID。
如果调用 spi_prepare 时给出了类型列表,那么在 spi_execp 的 query 之后必须给出一个长度完全相同的 Tcl 值列表。如果 spi_prepare 上的类型列表为空,则必须省略该参数。
如果查询是 SELECT 语句,则对 loop-body 和所选字段的变量,发生的情况与上文为 spi_exec 所述的相同。
下面是一个使用已预备计划的 PL/Tcl 函数示例:
CREATE FUNCTION t1_count(int4, int4) RETURNS int4 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" \\
int4 ]
}
spi_execp -count 1 $GD(plan) [ list $1 $2 ]
return $cnt
' LANGUAGE 'pltcl';
注意,在创建函数的查询中,Tcl 应看到的每个反斜杠都必须写成双份, 因为主解析器在 CREATE FUNCTION 上也会处理反斜杠。 给 spi_prepare 的查询字符串内部应当用美元符号标记参数位置,以免 $1 被第一次函数调用时给出的值替换。
PL/Tcl 对常用功能有特殊的支持。它识别两张 魔表 pltcl_modules 和 pltcl_modfuncs。 如果它们存在,模块 'unknown' 会在解释器创建之后立即被装载。每当调用一个未知的 Tcl 过程时,就会要求 unknown 过程检查该过程是否定义在某个模块中。如果是,则按需装载该模块。 要启用这一行为,PL/Tcl 调用处理器编译时必须设置 -DPLTCL_UNKNOWN_SUPPORT。
PL/Tcl 源码的 modules 子目录中有用于维护这些表的支持脚本,其中包括必须最初安装的 unknown 模块的源码。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。