Chinaunix首页 | 论坛 | 博客
  • 博客访问: 103569625
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-07 22:55:56

 






第十一章


PL/Tcl - TCL 过程语言


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

这个软件包最初是由 Jan Wieck 开发的。
▲ 概述

PL/Tcl 提供 C 语言里面函数开发者所拥有的大多数功能,只有一点点限制除外。好的限制是,所有东西都是在一个安全的 Tcl 解释器里面运行的。除了有限的安全的 Tcl 命令集外,只有很少的几个命令可以用于跨过 SPI 访问数据库以及通过 elog( ) 生成错误信息。不象 C 那样,(Tcl)没有办法访问数据库后端内部或者获得 OS 级的 Postgres 用户 ID 的权限。因此,任何非特权的数据库用户都可以被允许使用这种语言。

另外的(内部施加)的限制是 Tcl 过程不能创建用于新数据库类型的输入/输出函数。

如果在安装过程中的配置阶段打开了 Tcl/Tk 支持,那么用于 PL/Tcl 控制器的共享对象自动制作和安装在 Postgres 库目录里面。

▲ 描述<

Postgres 函数和 Tcl 过程名

在 Postgres 里,一个函数名可以用于不同的函数,只要这些函数的参数个数和类型不同即可.这一点将与 Tcl 过程名(命名规则)冲突。为了在 PL/Tcl 里提供同样的方便,在内部,Tcl 过程名包含该过程在 pg_proc 里的行对象标识(OID)做为它们的名字的一部分。因此,不同参数类型的 Postgres 函数对 Tcl 也是不同的。

用 PL/Tcl 定义函数

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

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

当在一个查询里面调用这个函数,参数是作为变量 $1 ... $n 传递给 Tcl 过程语言体的.所以一个简单的返回两个 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ˊ;

PL/Tcl 里的全局量

有时候(尤其是在使用下面描述的 SPI 函数的时候),在两个过程之间保存一些状态数据和非常有用的。所有在一个后端运行的 PL/Tcl 过程共享同一个安全 Tcl 解释器。为了避免一些 PL/Tcl 过程的副作用,每个过程可以通过 upvar 命令访问一个数组。此变量的全局名称是过程的内部名称,其局部名称是 GD。

PL/Tcl 里的触发器过程
在 Postgres 里的触发器过程定义为没有参数并且返回类型是opaque。在 PL/Tcl 语言里也是这样。触发器管理器传递给过程体的信息是通过下面变量传递的:

$TG_name
CREATE TRIGGER 语句里的触发器名称.

$TG_relid
导致触发器被调用的表的对象标识.

$TG_relatts
以一个空表元素为前缀的表里的字段名称的 Tcl 数组.所以用 lsearch Tcl 命令在数组里查找元素名称时,返回的从1开始计数的正整数与该字段在 pg_attribute 系统表里该字段的序号一样.

$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 语句里给出的参数一样的参数表.这些参数在过程体里可以通过 $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);
(译注:desc 在6.5以上版本里面是保留字,应该改成 describe 之类的东西.)

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


从 PL/Tcl 里访问数据库
我们可以用下面的命令从一个 PL/Tcl 过程体里面访问数据库:

elog level msg
产生一条日志信息。可能的级别是 NOTICE,WARN,ERROR,FATAL,DEBUG 和 NOIND,与用于 C 函数的 elog( ) 一样。

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 命令),它就会计算每个选择的行并且如期望的那样继续/中断。选择的字段的值被放到命名为列名称的变量里面去了.所以一个

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,该 ID 将被后继的 spi_execp 调用使用.参阅 spi_execp 中的例子.


spi_exec ?-count n? ?-arrayname? ?-nulls.string? query ?value-list? ?loop-body?

代入参数执行一个来自 spi_prepare 的规划.可选的 -count 数值告诉 spi_execp 可以被该查询处理的最大行数.
可选用于 -nulls 的值是一个空格字符串,并且 ‘n’ 字符告诉 spi_execp 哪一个数值是 NULL.如果给出该值,它必须包含数值个数的确切长度.
queryid 是 spi_prepare 调用返回的 ID.(译注:query?)

如果有一个类型列表给予了 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 给替换掉.

模块和未知的命令

PL/Tcl 对常用的东西有一个特殊的支持.它识别两个魔数表,pltcl_modules 和 pltcl_modfuncs.如果它们存在,模块 ‘unknown’在创建以后马上装载入解释器.当调用一个未知的 Tcl 过程时,未知的 proc 马上检查该过程是否在其中一个模块中定义了.如果的确定义了,该模块按要求装载进来.要打开这个特性,PL/Tcl 调用管理器必须带着 -DPLTCL_UNKNOWN_SUPPORT 设置编译.

在 PL/Tcl 源文件的模块子目录里有一些维护这些表的脚本,包括在最初必须安装的未知模块的源文件.



To be continued......


出处:南方Linux
阅读(857) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~