|
24.2. 描述24.2.1. PL/Tcl 函数和参数要用 PL/Tcl 语言创建一个函数,使用已知的语法 CREATE FUNCTION funcname ( argument-types ) RETURNS return-type AS ' # PL/Tcl function body ' LANGUAGE 'pltcl'; PL/TclU 是一样的,除了语言应该声明为 'pltclu' 之外.
函数体就是一段 Tcl 代码. 当在一个查询里面调用这个函数, 参数是作为变量 $1 ... $n 传递给 Tcl 脚本的. 结果是用通常的方法从 Tcl 代码中返回的,用一个 return 语句.比如, 一个简单的返回两个整数值的最大值函数可以这样定义: CREATE FUNCTION tcl_max (integer, integer) RETURNS integer AS ' if {$1 > $2} {return $1} return $2 ' LANGUAGE 'pltcl' WITH (isStrict); 请注意子句 WITH (isStrict) ,它让我们可以不用考虑 输入为 NULL 的情况∶如果传递了一个 NULL,该函数实际上就不会被调用, 而只是自动返回一个 NULL 结果. 如果是一个不严格的函数,如果一个参数的实际数值是 NULL, 那么对应的 $n 变量将被设置为一个空字串. 要检测一个特定的参数是否为 NULL,可以使用函数 argisnull . 比如,假设我们要求 tcl_max 在一个参数为 null 而另外一个为非 null 时 返回非 null 参数,而不是 NULL∶ CREATE FUNCTION tcl_max (integer, integer) RETURNS integer AS ' if {[argisnull 1]} { if {[argisnull 2]} { return_null } return $2 } if {[argisnull 2]} { return $1 } if {$1 > $2} {return $1} return $2 ' LANGUAGE 'pltcl';
如上所述,要从 PL/Tcl 函数中返回一个 NULL 数值, 可以执行 return_null .不管函数是否 严格,我们都可以这么做. 复合类型的参数是当做 Tcl 数组传递给过程的. 数组中的元素名字就是复合类型里的属性名字. 如果在实际的行中的一个属性有 NULL 数值,那么它不会在数组中出现! 下面是一个在 PL/Tcl 中定义了 overpaid_2 函数(和我们以前老的 PostgreSQL 文档中的一样)的例子 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';
目前没有返回一个复合类型结果值的支持. 24.2.2. PL/Tcl 里的数据值提供给 PL/Tcl 函数脚本的参数值都只是转换成文本形式 的输入参数(就象它们用 SELECT 语句显示出来的那样). 相反, return 可以用任何可以为函数所声明的返回类型 接受的输入格式的字串.因此,PL/Tcl 程序员可以把数据值当做文本操作. 24.2.3. PL/Tcl 里的全局量有时候 在两个过程之间保存一些状态数据和非常有用的. 因为所有在一个后端运行的 PL/Tcl 过程共享同一个安全 Tcl 解释器. 所以实现这个目标相当容易. 因此,任何全局 Tcl 变量都是可以被所有 PL/Tcl 过程调用访问的, 并且将在该次 SQL 客户端联接过程中保持一致.(请注意 PL/TclU 函数 也类似地共享全局数据,但是它们在一个不同的 Tcl 解释器里并且无法和 PL/Tcl 函数通讯. 为了保护 PL/Tcl 过程相互之间不至于互相干扰, 每个过程可以通过 upvar 命令访问一个全局数组. 此变量的全局名称是过程的内部名称,其局部名称是 GD . 我们建议使用 GD 作为存储过程的私有状态数据的数组. 而把普通的 Tcl 全局变量只用于那些你想在多个过程之间共享的变量. 一个使用 GD 的例子在下面的 spi_execp 例子里显示. 24.2.4. 在 PL/Tcl 里面访问数据库在 PL/Tcl 过程体里有下面的命令可以用于从访问数据库∶
24.2.5. PL/Tcl 里的触发器过程触发器过程可以用 PL/Tcl 写.和 PostgreSQL 的传统一样,要当做触发器调用的过程必需声明为没有参数并且返回 类型为 opaque 的函数. 触发器管理器传递给过程体的信息是通过下面变量传递的:
触发器过程返回的值是字符串 OK 或 SKIP 之一, 或者一个象 array get Tcl 命令返回的数组那样的东西. 如果返回值是 OK ,触发触发器的操作 (INSERT/UPDATE/DELETE)将会正常进行. SKIP 告诉触发器管理器不声不响地忽略该行的操作. 如果返回一个数组,那么它告诉 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 integer, description text, modcnt integer); CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt'); 请注意触发器过程本身并不知道字段名字;那些是从触发器参数中 提供的.这样就让我们可以将触发器过程复用于不同的表. 24.2.6. 模块和 unknown(未知) 的命令PL/Tcl 使用时支持自动装载 Tcl 代码. 它识别一个特殊的表, pltcl_modules ,该表被认为 包含 Tcl 代码的模块.如果存在这个表,则在创建完解释器后马上从该表中抓取 unknown 模块并装载到 Tcl 解释器中. 因为 unknown 模块实际上可以包含任何你需要的初始化脚本, 它通常是定义为一个 Tcl "unknown" 过程,在 Tcl 不能识别一个 调用的过程名的时候就调用它.PL/Tcl 这个过程的标准版本试图在 pltcl_modules 里找到一个定义所需要过程的模块. 如果找到一个,那么把它装载入解释器,然后允许继续按照原来的过程调用 处理.另外还定义了一个表 pltcl_modfuncs ,它提供了 哪个函数由哪个模块定义的索引,因此查找过程相当快. PostgreSQL 包括维护这些表的 支持脚本∶ pltcl_loadmod , pltcl_listmod , pltcl_delmod ,以及标准未知模块 share/unknown.pltcl 的源代码.这个模块可以一开始就 装载入每个数据库以便支持自动装载机制. 表 pltcl_modules 和 pltcl_modfuncs 必需 可以为所有人读取,但是把它做成只有数据库管理员可写并拥有是聪明的做法. 24.2.7. Tcl 过程名字在 PostgreSQL 里,同一个函数名字 可以用于不同的函数,只要参数个数或者它们的类型不同. 不过,Tcl 要求所有的过程名字都是唯一的. PL/Tcl 通过把内部 Tcl 过程名字包含该过程的 pg_proc 行的对象 ID 来处理这些问题.因此同名不同参数类型的 PostgreSQL 行数也将会有不同的 Tcl 过程名. 这个问题通常对 PL/Tcl 程序员而言不算啥,但是在调试的时候可能会看到. |