↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

不受支持的版本: 7.0 / 6.4
历史版本PostgreSQL 7.0 已于 2005 年 5 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本手册首页。

11.2. 描述

11.2.1. Postgres 函数与 Tcl 过程名

在 Postgres 中,只要参数个数或参数类型 不同,就可以复用同一个函数名。这会与 Tcl 过程名冲突。为了在 PL/Tcl 中提供同样的灵活性,内部 Tcl 过程名中包含该过程的 pg_proc 行的对象 ID 作为其名称的一部分。因此,同一个 Postgres 函数的不同参数类型版本对 Tcl 来说也是不同的。

11.2.2. 在 PL/Tcl 中定义函数

要用 PL/Tcl 语言创建函数,可使用标准语法

CREATE FUNCTION funcname argument-types) RETURNS return-type AS '
    # PL/Tcl function body
' LANGUAGE 'pltcl';
     

在查询中调用该函数时,参数以变量 $1 ... $n 的形式提供给 Tcl 过程体。因此,一个小小的 max 函数——返回两个 int4 值中较大的一个——可以这样创建:

CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
    if {$1 > $2} {return $1}
    return $2
' LANGUAGE 'pltcl';
     

复合类型参数会作为 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';
     

11.2.3. PL/Tcl 中的全局数据

有时(尤其是在使用稍后描述的 SPI 函数时)需要在两次过程调用之间保留某些全局状态数据。 同一后端中执行的所有 PL/Tcl 过程共享同一个安全的 Tcl 解释器。 为帮助防止 PL/Tcl 过程受到副作用影响, 每个过程都可通过 upvar 命令访问一个数组。该变量的全局名称是过程的内部 名称,局部名称是 GD。

11.2.4. PL/Tcl 中的触发器过程

在 Postgres 中,触发器过程定义为无参数、返回类型为 opaque 的函数。在 PL/Tcl 语言中也是如此。

来自触发器管理器的信息通过下列变量提供给过程体:

$TG_name

CREATE 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 时才有意义。

下面是一个简单的触发器过程示例,它强制用表中的一个整数值记录 对该行执行的更新次数(# of updates)。对于新插入的行,该值被初始化为 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, desc text);

CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
    FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
     

11.2.5. 从 PL/Tcl 访问数据库

在 PL/Tcl 过程体中可以使用下列命令来访问数据库:

elog level msg

发出一条日志消息。可用级别有 NOTICE、ERROR、 FATAL、DEBUG 和 NOIND,与 elog C 函数类似。

quote 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"
        
spi_exec ?-count 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 中所示。

spi_prepare query typelist

预备并保存一个查询计划供后续执行。它与 C 层的 SPI_prepare 有一点不同:该计划会被自动复制到顶层 内存上下文。因此,目前没有办法预备一个 计划而不保存它。

如果查询引用了参数,则必须以 Tcl 列表的形式给出类型名。spi_prepare 的返回值是一个查询 ID,供后续调用 spi_execp 时使用。示例见 spi_execp。

spi_exec ?-count 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 被第一次函数调用时给出的值替换。

模块与 unknown 命令

PL/Tcl 对常用功能有特殊的支持。它识别两张 魔表 pltcl_modules 和 pltcl_modfuncs。 如果它们存在,模块 'unknown' 会在解释器创建之后立即被装载。每当调用一个未知的 Tcl 过程时,就会要求 unknown 过程检查该过程是否定义在某个模块中。如果是,则按需装载该模块。 要启用这一行为,PL/Tcl 调用处理器编译时必须设置 -DPLTCL_UNKNOWN_SUPPORT。

PL/Tcl 源码的 modules 子目录中有用于维护这些表的支持脚本,其中包括必须最初安装的 unknown 模块的源码。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。