描述

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 源文件的模組子目錄裡有一些維護這些表的腳本,包括在最初必須安裝的未知模組的源文件。