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

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

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

49.3. PL/Tcl

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

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

49.3.1. 概述

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

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

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

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

49.3.2. 描述

49.3.2.1. Postgres 函数与 Tcl 过程名

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

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

49.3.2.3. PL/Tcl 中的全局数据

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

49.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');
    

49.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 模块的源码。

提交更正

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