PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

42.5. 从 PL/Tcl 访问数据库 #

在本节中,我们遵循 Tcl 的通常约定,使用问号而非方括号来表示语法 概要中的可选元素。以下命令可用于从 PL/Tcl 函数体中访问 数据库:

spi_exec ?-count n? ?-array name? command ?loop-body?

执行以字符串形式给出的 SQL 命令。 命令中的错误会导致错误被抛出。 否则,spi_exec 的返回值是 该命令处理(选择、插入、更新或删除)的行数,如果命令是实用工具 语句则为零。 此外,如果命令是 SELECT 语句,被选中列的值 会按下文所述放入 Tcl 变量中。

可选的 -count 值告诉 spi_exec 在取回 n 行后停止, 就像查询包含 LIMIT 子句那样。 如果 n 为零,查询会一直运行到 完成,与省略 -count 时相同。

如果命令是 SELECT 语句,结果列的值会被放入 以列名命名的 Tcl 变量中。 如果给出了 -array 选项,列值会被存储到 具名关联数组的元素中,列名用作数组索引。 此外,结果中的当前行号(从零计数)会被存储到名为 “.tupno” 的数组元素中,除非该 名称已被用作结果中的列名。

如果命令是 SELECT 语句且没有给出 loop-body 脚本,则只有第一行结果会被存储进 Tcl 变量或数组元素;其余行 (如果有)会被忽略。如果查询不返回任何行,则不发生任何存储。 (这种情况可以通过检查 spi_exec 的结果 来检测。)例如:

spi_exec "SELECT count(*) AS cnt FROM pg_proc"

会把 Tcl 变量 $cnt 设为 pg_proc 系统目录中的行数。

如果给出了可选的 loop-body 参数,它是 一段对查询结果中的每一行执行一次的 Tcl 脚本。 (如果给出的命令不是 SELECT, loop-body 会被忽略。) 当前行的各列的值会在每次迭代之前被存储进 Tcl 变量或数组 元素。例如:

spi_exec -array C "SELECT * FROM pg_class" {
    elog DEBUG "have table $C(relname)"
}

会为 pg_class 的每一行打印一条日志消息。 这一特性的工作方式与其他 Tcl 循环结构类似;特别地, continue 和 break 在循环 体内按通常方式工作。

如果查询结果的某个列为空,它的目标变量会被 “取消设置”(unset),而不是被设置。

spi_prepare query typelist

准备并保存一个查询计划以供稍后执行。 被保存的计划会在当前会话的生命期内保留。

查询可以使用参数,即每次实际执行计划时都要提供值的占位符。 在查询字符串中,用符号 $1 ... $n 引用参数。 如果查询使用参数,参数类型的名称必须以 Tcl 列表的形式给出。 (如果未使用参数,为 typelist 写一个空列表即可。)

spi_prepare 的返回值是一个查询 ID,用于 随后对 spi_execp 的调用。 示例见 spi_execp。

spi_execp ?-count n? ?-array name? ?-nulls string? queryid ?value-list? ?loop-body?

执行之前用 spi_prepare 准备的查询。 queryid 是 spi_prepare 返回的 ID。 如果查询引用了参数,必须提供一个 value-list。它是 参数实际值的 Tcl 列表。该列表的长度必须与之前给 spi_prepare 的参数类型列表相同。 如果查询没有参数,则省略 value-list。

-nulls 的可选值是一个由空格和 'n' 字符组成的字符串,告诉 spi_execp 哪些参数是空值。 如果给出,它的长度必须与 value-list 完全相同。 如果未给出,所有参数值都非空。

除了查询及其参数的指定方式之外, 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;

我们需要在传给 spi_prepare 的查询字符串内部使用反斜杠, 以确保 $n 标记会按原样 传递给 spi_prepare,而不被 Tcl 变量替换 掉。

subtransaction command

包含在 command 中的 Tcl 脚本会在 一个 SQL 子事务内执行。 如果脚本返回一个错误,整个子事务会在把错误返回给外围 Tcl 代码 之前被回滚。 更多细节和示例见 Section 42.9。

quote string

把给定字符串中出现的所有单引号和反斜杠字符都加倍。 这可以用来安全地为要插入到给 spi_exec 或 spi_prepare 的 SQL 命令中的字符串加 引号。 例如,考虑一个形如下式的 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 命令字符串的一部分 解析。

elog level msg

发出一条日志或错误消息。可能的级别有 DEBUG、LOG、 INFO、 NOTICE、WARNING、 ERROR 和 FATAL。ERROR 会抛出错误条件;如果外围 Tcl 代码没有捕获它,该错误会传播到 调用查询,导致当前事务或子事务被中止。 这实际上与 Tcl 的 error 命令相同。 FATAL 会中止事务并导致当前会话关闭。 (在 PL/Tcl 函数中使用这个错误级别可能没有什么好理由,但为了 完整性还是提供了它。) 其他级别只会生成不同优先级的消息。 特定优先级的消息是否被报告给客户端、写入服务器日志或两者兼有, 由 log_min_messages 和 client_min_messages 配置 变量控制。更多信息见 Chapter 19 和 Section 42.8。