↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

已结束支持的版本: 7.0 / 6.5
历史版本。 PostgreSQL 6.5 已结束支持。 请参阅 当前版本手册.

42.3. PL/Tcl

PL/Tcl 是一个可装载的 Postgres 数据库系统过程语言,它使 Tcl 语言可以用来创建函数和触发器过程。

这个软件包最初由 Jan Wieck 编写。

42.3.1. 概述

PL/Tcl 提供了函数编写者在 C 语言中所具备的大部分能力,但有一些限制。

好的限制是所有内容都在一个安全的 Tcl 解释器中执行。除了安全 Tcl 的有限命令集之外,只有少数命令可用于经 SPI 访问数据库以及通过 elog() 发出消息。没有办法像 C 语言那样访问数据库后端的内部,也无法获得 Postgres 用户 ID 权限下的 OS 级访问。因此,可以允许任何非特权数据库用户使用这一语言。

另一项内在的限制是,Tcl 过程不能用来为新数据类型创建输入/输出函数。

如果在安装过程的配置步骤中指定了 Tcl/Tk 支持,PL/Tcl 调用处理器的共享对象就会自动构建并安装到 Postgres 库目录中。

42.3.2. 描述

42.3.2.1. Postgres 函数与 Tcl 过程名

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

42.3.2.2. 在 PL/Tcl 中定义函数

要用 PL/Tcl 语言创建函数,使用已知的语法

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

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

    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';
    

42.3.2.3. PL/Tcl 中的全局数据

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

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

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

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

$TG_name

来自 CREATE TRIGGER 语句的触发器名称。

$TG_relid

导致触发器过程被调用的表的对象 ID。

$TG_relatts

表字段名组成的 Tcl 列表,前面带有一个空列表元素。因此用 lsearch Tcl 命令在列表中查找元素名时,返回的正数编号与字段在 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

按CREATE TRIGGER 语句给出的传给过程的参数组成的 Tcl 列表。这些参数在过程体中也可以用 $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, desc text);

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

42.3.2.5. 从 PL/Tcl 访问数据库

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

elog level msg

发出一条日志消息。可能的级别有 NOTICE、WARN、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 语句并且给出了可选的循环体(一组 Tcl 命令,就像在 foreach 语句中那样),则对选出的每一行求值,并且在 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? ?-array name? ?-nulls str? query ?valuelist? ?loop-body?

执行 spi_prepare 预备的计划并做变量替换。可选的 -count 值告诉 spi_execp 查询要处理的最大行数。

-nulls 的可选值是由空格和 'n' 字符组成的字符串,告诉 spi_execp 哪些值是 NULL。如果给出,它的长度必须恰好等于值的个数。

queryid 是 spi_prepare 调用返回的 ID。

如果 spi_prepare 给出了类型列表,则必须在查询之后给 spi_execp 一个长度恰好相同的 Tcl 值列表。如果 spi_prepare 的类型列表为空,则必须省略这个参数。

如果查询是 SELECT 语句,循环体和选出字段的变量的处理方式与 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 模块的源码。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.