PostgreSQL7.0手冊-用戶手冊-11. PL/Tcl - TCL 過程語言
2019-09-08 23:33:10
供稿:網(wǎng)友
第十一章. PL/Tcl - TCL 過程語言
內(nèi)容
概述
描述
PL/Tcl 是一種用于 Postgres 數(shù)據(jù)庫系統(tǒng)的可裝載的過程化語言,它讓我們可以用 Tcl 語言來創(chuàng)建函數(shù)和觸發(fā)器過程.
這個軟件包最初是由 Jan Wieck 開發(fā)的.
概述
PL/Tcl 提供 C 語言里面函數(shù)開發(fā)者所擁有的大多數(shù)功能,只有一點點限制除外.
好的限制是,所有東西都是在一個安全的 Tcl 解釋器里面運行的.除了有限的安全的 Tcl 命令集外,只有很少的幾個命令可以用于跨過 SPI 訪問數(shù)據(jù)庫以及通過 elog() 生成錯誤信息.不象 C 那樣,(Tcl)沒有辦法訪問數(shù)據(jù)庫后端內(nèi)部或者獲得 OS 級的 Postgres 用戶 ID 的權(quán)限.因此,任何非特權(quán)的數(shù)據(jù)庫用戶都可以被允許使用這種語言.
另外的(內(nèi)部施加)的限制是 Tcl 過程不能創(chuàng)建用于新數(shù)據(jù)庫類型的輸入/輸出函數(shù).
如果在安裝過程中的配置階段打開了 Tcl/Tk 支持,那么用于 PL/Tcl 控制器的共享對象自動制作和安裝在 Postgres 庫目錄里面.
--------------------------------------------------------------------------------
描述
Postgres 函數(shù)和 Tcl 過程名
在 Postgres 里,一個函數(shù)名可以用于不同的函數(shù),只要這些函數(shù)的參數(shù)個數(shù)和類型不同即可.這一點將與 Tcl 過程名(命名規(guī)則)沖突.為了在 PL/Tcl 里提供同樣的方便,在內(nèi)部,Tcl 過程名包含該過程在 pg_proc 里的行對象標識(OID)做為它們的名字的一部分.因此,不同參數(shù)類型的 Postgres 函數(shù)對 Tcl 也是不同的.
用 PL/Tcl 定義函數(shù)
要用 PL/Tcl 語言創(chuàng)建一個函數(shù),使用已知的語法
CREATE FUNCTION funcname argument-types) RETURNS return-type AS '
# PL/Tcl function body
' LANGUAGE 'pltcl';
當在一個查詢里面調(diào)用這個函數(shù),參數(shù)是作為變量 $1 ... $n 傳遞給 Tcl 過程語言體的.所以一個簡單的返回兩個 int4 值的最大值函數(shù)可以這樣創(chuàng)建:
CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
if {$1 > $2} {return $1}
return $2
' LANGUAGE 'pltcl';
復合類型參數(shù)是作為 Tcl 數(shù)組賦予過程的.數(shù)組的元素名稱就是復合類型的字段名稱.如果一個實際行的字段是一個 NULL 值,它將不在數(shù)組中出現(xiàn)!這里是一個用 PL/Tcl 定義 overpaid_2 函數(shù)的例子(本例可以在舊的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 函數(shù)的時候),在兩個過程之間保存一些狀態(tài)數(shù)據(jù)和非常有用的.所有在一個后端運行的 PL/Tcl 過程共享同一個安全 Tcl 解釋器.為了避免一些 PL/Tcl 過程的副作用,每個過程可以通過 upvar 命令訪問一個數(shù)組.此變量的全局名稱是過程的內(nèi)部名稱,其局部名稱是 GD.
PL/Tcl 里的觸發(fā)器過程
在 Postgres 里的觸發(fā)器過程定義為沒有參數(shù)并且返回類型是opaque.在 PL/Tcl 語言里也是這樣.
觸發(fā)器管理器傳遞給過程體的信息是通過下面變量傳遞的:
$TG_name
CREATE TRIGGER 語句里的觸發(fā)器名稱.
$TG_relid
導致觸發(fā)器被調(diào)用的表的對象標識.
$TG_relatts
以一個空表元素為前綴的表里的字段名稱的 Tcl 數(shù)組.所以用 lsearch Tcl 命令在數(shù)組里查找元素名稱時,返回的從1開始計數(shù)的正整數(shù)與該字段在 pg_attribute 系統(tǒng)表里該字段的序號一樣.
$TG_when
由觸發(fā)器調(diào)用事件決定的字符串 BEFORE 或 AFTER .
$TG_level
由觸發(fā)器調(diào)用事件決定的字符串 ROW 或 STATEMENT .
$TG_op
由觸發(fā)器調(diào)用事件決定的字符串 INSERT,UPDATE 或 DELETE .
$NEW
在 INSERT/UPDATE 時一個包含表的新行的數(shù)組或在 DELETE 時的一個空數(shù)組.
$OLD
在 UPDATE/DELETE 時一個包含表的舊行的數(shù)組或在 INSERT 時的一個空數(shù)組.
$GD
前面所述的全局狀態(tài)數(shù)據(jù)數(shù)組.
$args
如同在 CREATE TRIGGER 語句里給出的參數(shù)一樣的參數(shù)表.這些參數(shù)在過程體里可以通過 $1 ... $n 來訪問.
觸發(fā)器過程返回的值是字符串 OK 或 SKIP 之一,或者一個象 'array get' Tcl 命令返回的數(shù)組.如果返回值是 OK,觸發(fā)觸發(fā)器的操作(INSERT/UPDATE/DELETE)將會發(fā)生.顯然,SKIP 告訴觸發(fā)器管理器隱式的忽略操作.從 'array get' 來的數(shù)組告訴 PL/Tcl 返回一個修改后的行給觸發(fā)器管理器,該行將代替在 $NEW (只在 INSERT/UPDATE 中)中給出的行.當然,這些只有在觸發(fā)器是 BEFORE 和 FOR EACH ROW 時才有意義.
下面是一個小的觸發(fā)器過程的例子,它強制表內(nèi)的一個整數(shù)值對行的更新次數(shù)進行跟蹤.對插入的新行,該值初始化為 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以上版本里面是保留字,應(yīng)該改成 describe 之類的東西.)
CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
從 PL/Tcl 里訪問數(shù)據(jù)庫
我們可以用下面的命令從一個 PL/Tcl 過程體里面訪問數(shù)據(jù)庫:
elog level msg
產(chǎn)生一條日志信息.可能的級別是 NOTICE,WARN,ERROR,F(xiàn)ATAL,DEBUG 和 NOIND,與用于 C 函數(shù)的 elog() 一樣.
quote string
復制所有出現(xiàn)的單引號和反斜杠字符.當賦予 spi_exec 或 spi_prepare (不用于 spi_execp 使用的數(shù)組)的查詢字符串里使用了變量時就要使用這個命令.想象下面的查詢字符串
"SELECT '$val' AS ret"
如果 Tcl 變量 val 實際包含 "doesn't".這樣會導致最終的查詢字符串結(jié)果
"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?
調(diào)用 分析器/規(guī)劃器(調(diào)度器)/優(yōu)化器/執(zhí)行器運行查詢.可選的 -count 值告訴 spi_exec 該查詢可以處理的最大行數(shù).
如果查詢是一個 SELECT 語句并且給出了可選的循環(huán)體(一個 Tcl 命令的語句體,象一個 foreach 命令),它就會計算每個選擇的行并且如期望的那樣繼續(xù)/中斷。選擇的字段的值被放到命名為列名稱的變量里面去了.所以一個
spi_exec "SELECT count(*) AS cnt FROM pg_proc"
將把變量 $cnt 置為 pg_proc 系統(tǒng)表里的行數(shù).如果給出了可選的 -array ,列/字段的值將保存在相關(guān)的名為 'name' 的數(shù)組里,而不是分離的變量.
spi_exec -array C "SELECT * FROM pg_class" {
elog DEBUG "have table $C(relname)"
}
將為 pg_class 的每一行打印一個 DEBUG 日志信息.spi_exec 返回的值是查詢涉及到的保存在全局變量 SPI_processed 里的行數(shù).
spi_prepare query typelist
為后面執(zhí)行準備并且保存一個查詢規(guī)劃.這里與 C 級別的 SPI_prepare 有一些小區(qū)別,就是該規(guī)劃將自動拷貝到頂級存儲器環(huán)境.因此,目前沒有辦法準備一個規(guī)劃而不存儲它.
如果查詢引用了參數(shù),類型名必須做為 Tcl 數(shù)組給出.從 spi_prepare 返回的值是一個查詢 ID,該 ID 將被后繼的 spi_execp 調(diào)用使用.參閱 spi_execp 中的例子.
spi_exec ?-count n? ?-arrayname? ?-nulls.string? query ?value-list? ?loop-body?
代入?yún)?shù)執(zhí)行一個來自 spi_prepare 的規(guī)劃.可選的 -count 數(shù)值告訴 spi_execp 可以被該查詢處理的最大行數(shù).
可選用于 -nulls 的值是一個空格字符串,并且 'n' 字符告訴 spi_execp 哪一個數(shù)值是 NULL.如果給出該值,它必須包含數(shù)值個數(shù)的確切長度.
queryid 是 spi_prepare 調(diào)用返回的 ID.(譯注:query?)
如果有一個類型列表給予了 spi_prepare,必須在查詢后面給 spi_execp 一個相同長度的 Tcl 數(shù)值列表(數(shù)組).如果 spi_prepare 里的類型表是空的,此參數(shù)必須忽略.
如果查詢是一個 SELECT 語句,有與 spi_exec 里描述的循環(huán)體和用于所選的字段的變量有一樣的現(xiàn)象.
這里是一個使用準備好了的規(guī)劃的 PL/Tcl 函數(shù)例子:
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';
注意創(chuàng)建函數(shù)時每個 Tcl 會看到的反斜杠必須寫雙份,因為在 CREATE FUNCTION 時主分析器也處理反斜杠.在給予 spi_prepare 的查詢字符串里面應(yīng)該是真正的標識參數(shù)位置的美圓符號,而不應(yīng)讓第一次函數(shù)調(diào)用給出的值把 $1 給替換掉.
模塊和未知的命令
PL/Tcl 對常用的東西有一個特殊的支持.它識別兩個魔數(shù)表,pltcl_modules 和 pltcl_modfuncs.如果它們存在,模塊 'unknown' 在創(chuàng)建以后馬上裝載入解釋器.當調(diào)用一個未知的 Tcl 過程時,未知的 proc 馬上檢查該過程是否在其中一個模塊中定義了.如果的確定義了,該模塊按要求裝載進來.要打開這個特性,PL/Tcl 調(diào)用管理器必須帶著 -DPLTCL_UNKNOWN_SUPPORT 設(shè)置編譯.
在 PL/Tcl 源文件的模塊子目錄里有一些維護這些表的腳本,包括在最初必須安裝的未知模塊的源文件.
--------------------------------------------------------------------------------