<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 35.9\. C-語言函數 用戶定義的函數可以用 C 寫(或者是與C兼容的語言,比如C++)。 這樣的函數被編譯進動態加載對象(共享庫)并且由服務器根據需要加載。 動態加載的特性是"C 語言函數"和"內部函數"之間的區別—不過, 實際的編碼習慣在兩者之間實際上是一樣的。因此, 標準的內部函數庫為寫用戶定義C函數提供了大量最好的樣例。 目前對 C 函數有兩種調用約定。新的"版本-1"的調用約定是通過為該函數書寫一個 `PG_FUNCTION_INFO_V1()`宏來標識的,像下面演示的那樣。缺少這個宏表示一個 老風格的("版本-0")函數。兩種風格里在`CREATE FUNCTION`里聲明的都是`C`。 現在老風格的函數已經廢棄了,主要是因為移植性原因和缺乏功能, 不過出于兼容性原因,系統仍然支持它。 ## 35.9.1\. 動態加載 當用戶定義的函數第一次被服務器會話調用時, 動態加載器才把可加載對象文件里的函數目標碼加載進內存。 因此,用于用戶定義 C 函數的`CREATE FUNCTION`必須為函數聲明兩個信息: 可加載對象文件名、在目標文件里調用的 C 函數名(連接符號)。 如果沒有明確聲明 C 函數名,那么就假設它與 SQL 函數名相同。 基于在`CREATE FUNCTION`命令中給出的名字, 下面的算法用于定位共享對象文件: 1. 如果名字是一個絕對路徑,則加載給出的文件。 2. 如果名字以字符串`$libdir`開頭, 那么該部分將被PostgreSQL庫目錄名代替, 該目錄是在編譯時確定的。 3. 如果名字不包含目錄部分, 那么在配置參數[dynamic_library_path](#calibre_link-980). 聲明的路徑里查找。 4. 如果沒有在路徑里找到該文件,或者它包含一個非絕對目錄部分, 那么動態加載器就會試圖直接拿這個名字來加載, 這樣幾乎可以肯定是要失敗的(依靠當前工作目錄是不可靠的)。 如果這個順序不管用,那么就給這個名字加上平臺相關的共享庫文件擴展名(通常是`.so`), 然后再重新按照上面的過程找一遍。如果還是失敗,那么加載失敗。 建議使用相對于`$libdir`的目錄或者通過動態庫路徑定位共享庫。 這樣,如果新版本安裝在一個不同的位置,那么就可以簡化版本升級。 `$libdir`的實際目錄位置可以 用`pg_config --pkglibdir`命令找到。 運行PostgreSQL服務器的用戶ID 必須可以遍歷路徑到達想加載的文件。 一個常見的錯誤就是把該文件或者一個高層目 錄的權限設置為postgres用戶不可讀和/或不能執行。 在任何情況下,在`CREATE FUNCTION`命令里給出的文件 名是在系統表里按照文本記錄的, 因此,如果需要再次加載,那么會再次運行這個過程。 > **Note:** PostgreSQL不會自動編譯 C 函數; 在使用`CREATE FUNCTION`命令之前你必須編譯它。 參閱[Section 35.9.6](#calibre_link-926)獲取更多信息。 為了確保不會錯誤加載共享庫文件,從PostgreSQL 開始將檢查那個文件的"magic block",這允許服務器以檢查明顯的不兼容性。 比如不同版本PostgreSQL的編譯代碼。 magic block需要被作為PostgreSQL 8.2。 為了包含"magic block", 請在包含了`fmgr.h`頭文件之后, 將下面的內容寫進一個(也只能是一個)模塊的源代碼文件中: ``` #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif ``` 如果不打算兼容8.2 PostgreSQL之前的版本, `#ifdef`測試也可以省略。 動態加載對象文件在首次使用之后將一直滯留在內存中。 在同一個會話中的下一次調用將只需查找符號表的很小開銷。 如果你想強制重新加載(比如重新編譯之后), 可以重新開始一個新的會話。 動態加載文件也可以包含初始化函數和結束函數。 如果包含一個名為`_PG_init`的函數,那么該函數將在該文件被加載后立即執行, 該函數不能接受任何參數并且必須返回 void 。 如果包含一個名為`_PG_fini`的函數,那么該函數將在該文件即將被卸載前執行, 同樣,該函數不能接受任何參數并且必須返回 void 。 需要注意的是`_PG_fini`僅在該文件即將被卸載前執行而不是在會話結束的時候執行。 目前,卸載被禁止并且將不會發生,但是這可能在將來改變。 ## 35.9.2\. 基本類型的C語言函數 要知道如何寫C語言函數,就必須知道PostgreSQL在 內部如何表現基本數據類型以及如何傳入及傳出函數。 PostgreSQL內部把基本類型當作"一塊內存"看待。 定義在某種類型上的用戶定義函數實際上定義了 PostgreSQL對該數據類型可能的操作。也就是說, PostgreSQL只是從磁盤讀取和存儲該數據 類型并使用你定義的函數來輸入、處理、輸出數據。 基本類型可以有下面三種內部形態(格式)之一: * 傳遞數值,定長 * 傳遞引用,定長 * 傳遞引用,變長 傳遞數值的類型長度只能是1, 2, 4字節。如果`sizeof(Datum)` 在你的機器上是8的話,那么還有8字節。你要仔細定義你的類型, 確保它們在任何體系平臺上都是相同尺寸(字節)。例如,`long` 是一個危險的類型, 因為在一些機器上它是4字節而在另外一些機器上是8字節, 而`int`在大多數Unix機器上都是4字節的。 在一個Unix機器上的`int4` 合理實現可能是: ``` /* 4-byte integer, passed by value */ typedef int int4; ``` 實際PostgreSQL C代碼調用此`int32`類型, 因為它是C中的慣例,`int``_XX_` 意味著`_XX_` _bits_。 注意因此C類型`int8`的大小是1字節。SQL類型`int8`被稱為C中`int64`。參見 [Table 35-1](#calibre_link-981)。 另外,任何尺寸的定長類型都可以是傳遞引用型。例如, 下面是一個PostgreSQL類型的實現: ``` /* 16-byte structure, passed by reference */ typedef struct { double x, y; } Point; ``` 只能使用指向這些類型的指針在PostgreSQL函數里傳入和傳出數據。 要返回這樣類型的值,用`palloc`分配正確數量的內存,填充這些內存, 然后返回一個指向它的指針。如果只是想返回和輸入參數類型與數值都相同的數值, 可以忽略額外的`palloc`,只要返回指向輸入數值的指針就行。 最后,所有變長類型同樣也只能通過引用來傳遞。 所有變長類型必須以一個4字節的長度域開始,通過`SET_VARSIZE`設置, 沒有直接設置這個字段! 所有存儲在該類型中的數據必須放在緊接著長度域的存儲空間里。 長度域是結構的全長,也就是說,包括長度域本身的長度。 另外一個重要的點是避免數據類型值中留下未初始化的位;比如,請注意任何對齊填充字節 溢出的零可能出現在結構體中。沒有這些,你的數據類型的邏輯上等價常量可能被規劃器看做是 不平等的,導致低效(雖然是不正確的)規劃。 | **Warning** | |:--- | | _絕對不要_修改一個引用傳遞的輸入值,否則很可能破壞磁盤上的數據。 因為指針很可能直接指向一個磁盤緩沖區。 這條規則的唯一例外在[Section 35.10](#calibre_link-836)里。 | 比如,我們可以用下面的方法定義一個`text`類型: ``` typedef struct { int32 length; char data[1]; } text; ``` 顯然,上面聲明的數據域長度不足以存儲任何可能的字符串。 因為在C中不可能聲明變長結構,所以我們倚賴這樣的知識: C編譯器不會對數組下標進行范圍檢查。只需要分配足夠的空間, 然后把數組當做已經聲明為合適長度的變量訪問。這是一個常用的技巧, 你可以在許多C教科書中讀到。 當處理變長類型時,必須仔細分配正確的內存數量并正確設置長度域。 例如,如果想在一個`text`結構里存儲40字節, 我們可能會使用像下面的代碼片段: ``` #include "postgres.h" ... char buffer[40]; /* our source data */ ... text *destination = (text *) palloc(VARHDRSZ + 40); SET_VARSIZE(destination, VARHDRSZ + 40); memcpy(destination->data, buffer, 40); ... ``` `VARHDRSZ`等價于`sizeof(int32)`, 但是我們認為用宏`VARHDRSZ`表示附加尺寸是用于變長類型的更好風格。 同時,該長度字段_必須_使用`SET_VARSIZE`宏設置, 而不是簡單的賦值。 [Table 35-1](#calibre_link-981)列出了書寫 使用PostgreSQL內置類型的 C 函數里需要知道的 SQL 類型與 C 類型的對應關系。"定義在" 列給出了需要包含以獲取該類型定義的頭文件。 實際定義可能在列表文件中包含的不同文件中。我們建議用戶堅持定義的接口。 注意,你應該總是首先包括`postgres.h`, 因為它聲明了許多你需要的東西。 **Table 35-1\. 與內建SQL類型等效的C類型** | SQL Type | C Type | Defined In | | --- | --- | --- | | `abstime` | `AbsoluteTime` | `utils/nabstime.h` | | `boolean` | `bool` | `postgres.h` (可能編譯器內置) | | `box` | `BOX*` | `utils/geo_decls.h` | | `bytea` | `bytea*` | `postgres.h` | | `"char"` | `char` | (編譯器內置) | | `character` | `BpChar*` | `postgres.h` | | `cid` | `CommandId` | `postgres.h` | | `date` | `DateADT` | `utils/date.h` | | `smallint` (`int2`) | `int16` | `postgres.h` | | `int2vector` | `int2vector*` | `postgres.h` | | `integer` (`int4`) | `int32` | `postgres.h` | | `real` (`float4`) | `float4*` | `postgres.h` | | `double precision` (`float8`) | `float8*` | `postgres.h` | | `interval` | `Interval*` | `datatype/timestamp.h` | | `lseg` | `LSEG*` | `utils/geo_decls.h` | | `name` | `Name` | `postgres.h` | | `oid` | `Oid` | `postgres.h` | | `oidvector` | `oidvector*` | `postgres.h` | | `path` | `PATH*` | `utils/geo_decls.h` | | `point` | `POINT*` | `utils/geo_decls.h` | | `regproc` | `regproc` | `postgres.h` | | `reltime` | `RelativeTime` | `utils/nabstime.h` | | `text` | `text*` | `postgres.h` | | `tid` | `ItemPointer` | `storage/itemptr.h` | | `time` | `TimeADT` | `utils/date.h` | | `time with time zone` | `TimeTzADT` | `utils/date.h` | | `timestamp` | `Timestamp*` | `datatype/timestamp.h` | | `tinterval` | `TimeInterval` | `utils/nabstime.h` | | `varchar` | `VarChar*` | `postgres.h` | | `xid` | `TransactionId` | `postgres.h` | 既然我們已經討論了基本類型所有可能的結構, 我們便可以用實際的函數舉一些例子。 ## 35.9.3\. 版本-0調用約定 先提供現在已經不提倡了的"老風格"—因為比較容易邁出第一步。 在版本-0方法中,此風格 C 函數的參數和結果用普通 C 風格聲明, 但是要小心使用上面顯示的 SQL 數據類型的 C 表現形式。 下面是一些例子: ``` #include "postgres.h" #include <string.h> #include "utils/geo_decls.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /* 傳遞數值 */ int add_one(int arg) { return arg + 1; } /* 傳遞引用,定長 */ float8 * add_one_float8(float8 *arg) { float8 *result = (float8 *) palloc(sizeof(float8)); *result = *arg + 1.0; return result; } Point * makepoint(Point *pointx, Point *pointy) { Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; return new_point; } /* 傳遞引用,變長*/ text * copytext(text *t) { /* * VARSIZE是結構以字節計的總長度。 */ text *new_t = (text *) palloc(VARSIZE(t)); SET_VARSIZE(new_t, VARSIZE(t)); /* * VARDATA是結構中一個指向數據區的指針。 */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t) - VARHDRSZ); /* how many bytes */ return new_t; } text * concat_text(text *arg1, text *arg2) { int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ); memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ), VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ); return new_text; } ``` 假設上面的代碼放在`funcs.c`文件中并且編譯成了共享目標, 我們可以用下面的命令為PostgreSQL定義這些函數: ``` CREATE FUNCTION add_one(integer) RETURNS integer AS '_DIRECTORY_/funcs', 'add_one' LANGUAGE C STRICT; --注意:重載了名字為"add_one"的 SQL 函數 CREATE FUNCTION add_one(double precision) RETURNS double precision AS '_DIRECTORY_/funcs', 'add_one_float8' LANGUAGE C STRICT; CREATE FUNCTION makepoint(point, point) RETURNS point AS '_DIRECTORY_/funcs', 'makepoint' LANGUAGE C STRICT; CREATE FUNCTION copytext(text) RETURNS text AS '_DIRECTORY_/funcs', 'copytext' LANGUAGE C STRICT; CREATE FUNCTION concat_text(text, text) RETURNS text AS '_DIRECTORY_/funcs', 'concat_text' LANGUAGE C STRICT; ``` 這里的`_DIRECTORY_`代表共享庫文件的目錄, 比如包含本節示例代碼的PostgreSQL教程目錄。 更好的風格應該是將`_DIRECTORY_`加到搜索路徑之后, 在`AS`子句里只使用`'funcs'`,不管怎樣, 我們都可以省略和系統相關的共享庫擴展, 通常是`.so`或者`.sl`。 請注意我們把函數聲明為"strict"(嚴格),意思是說如果任何輸入值為NULL, 那么系統應該自動假設一個NULL的結果。這樣處理可以讓我們避免在函數代碼里面檢查 NULL輸入。如果不這樣處理,我們就得明確檢查NULL, 比如為每個傳遞引用的參數檢查空指針。對于傳值類型的參數,我們甚至沒有辦法檢查! 盡管這種老調用風格用起來簡單,但它卻不太容易移植; 在一些系統上,用這種方法傳遞比`int`小的數據類型就會碰到困難。 而且,我們沒有很好的返回NULL結果的辦法,也沒有除了把函數嚴格化以外的處理 NULL參數的方法。版本-1約定,下面要講的新方法則解決了這些問題。 ## 35.9.4\. 版本1調用約定 版本-1調用約定使用宏消除大多數傳遞參數和結果的復雜性。版本-1風格函數的C定義總是下面這樣: ``` Datum funcname(PG_FUNCTION_ARGS) ``` 另外,宏調用: ``` PG_FUNCTION_INFO_V1(funcname); ``` 也必須出現在同一個源文件里(通常就可以寫在函數自身前面)。 對那些`internal`語言函數而言,不需要調用這個宏, 因為PostgreSQL目前假設內部函數都是版本-1。不過,對于動態加載的函數, 它是必須的。 在版本-1 函數里,每個實際參數都是用一個對應該參數的數據類型的 `PG_GETARG_`_xxx_`()`宏抓取的, 用返回類型的`PG_RETURN_`_xxx_`()`宏返回結果。 `PG_GETARG_`_xxx_`()`接受要抓取的函數參數的編號 (從 0 開始)作為其參數。`PG_RETURN_`_xxx_`()` 接受實際要返回的數值為自身的參數。 下面是和上面一樣的函數,但是使用版本-1風格編寫的: ``` #include "postgres.h" #include <string.h> #include "fmgr.h" #include "utils/geo_decls.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /*傳遞數值*/ PG_FUNCTION_INFO_V1(add_one); Datum add_one(PG_FUNCTION_ARGS) { int32 arg = PG_GETARG_INT32(0); PG_RETURN_INT32(arg + 1); } /*傳遞引用,定長*/ PG_FUNCTION_INFO_V1(add_one_float8); Datum add_one_float8(PG_FUNCTION_ARGS) { /*用于FLOAT8的宏,隱藏其傳遞引用的本質。*/ float8 arg = PG_GETARG_FLOAT8(0); PG_RETURN_FLOAT8(arg + 1.0); } PG_FUNCTION_INFO_V1(makepoint); Datum makepoint(PG_FUNCTION_ARGS) { /* 這里,我們沒有隱藏Point的傳遞引用的本質*/ Point *pointx = PG_GETARG_POINT_P(0); Point *pointy = PG_GETARG_POINT_P(1); Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; PG_RETURN_POINT_P(new_point); } /*傳遞引用,變長*/ PG_FUNCTION_INFO_V1(copytext); Datum copytext(PG_FUNCTION_ARGS) { text *t = PG_GETARG_TEXT_P(0); /* * VARSIZE是結構以字節計的總長度。 */ text *new_t = (text *) palloc(VARSIZE(t)); SET_VARSIZE(new_t, VARSIZE(t)); /* * VARDATA是結構中指向數據區的一個指針。 */ memcpy((void *) VARDATA(new_t), /* 目的*/ (void *) VARDATA(t), /* 源 */ VARSIZE(t) - VARHDRSZ); /* 多少字節 */ PG_RETURN_TEXT_P(new_t); } PG_FUNCTION_INFO_V1(concat_text); Datum concat_text(PG_FUNCTION_ARGS) { text *arg1 = PG_GETARG_TEXT_P(0); text *arg2 = PG_GETARG_TEXT_P(1); int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ); memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ), VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ); PG_RETURN_TEXT_P(new_text); } ``` 用到的`CREATE FUNCTION`命令和版本-0等效命令一樣。 猛一看,版本-1的編碼好像只是無目的地蒙人。但是它的確給我們許多改進, 因為宏可以隱藏許多不必要的細節。一個例子在`add_one_float8`的編碼里, 這里我們不再需要不停叮囑自己`float8`是傳遞引用類型。 另外一個例子是用于變長類型的宏`GETARG`隱藏了抓取 "非常規"(壓縮的或者超長的)值需要做的處理。 版本-1的函數另一個巨大的改進是對NULL輸入和結果的處理。 宏`PG_ARGISNULL(`_n_`)`允許一個函數測試每個輸入是否為NULL, 當然,這只是對那些沒有聲明為"strict"的函數有必要。 因為如果有`PG_GETARG_`_xxx_`()`宏, 輸入參數是從零開始計算的。 請注意我們不應該執行`PG_GETARG_`_xxx_`()`, 除非有人聲明了參數不是NULL。 要返回一個NULL結果,可以執行一個`PG_RETURN_NULL()`, 這樣對嚴格的和不嚴格的函數都有效。 在新風格的接口中提供的其它選項是`PG_GETARG_`_xxx_`()`宏的兩個變種。 第一個變體`PG_GETARG_`_xxx_`_COPY()` 保證返回一個指定參數的副本,該副本是可以安全地寫入的。普通的宏有時候會返回一個指向物理存儲在表中的某值的指針, 因此我們不能寫入該指針。用`PG_GETARG_`_xxx_`_COPY()`宏保證獲取一個可寫的結果。 第二個變體由`PG_GETARG_`_xxx_`_SLICE()`宏組成, 它接受三個參數。第一個是參數的個數(與上同)。第二個和第三個是要返回的偏移量和數據段的長度。 偏移是從零開始計算的,一個負數的長度則要求返回該值的剩余長度的數據。 這些過程提供了訪問大數據值的中一部分的更有效方法, 特別是數據的存儲類型是"external"的時候。 一個字段的存儲類型可以用`ALTER TABLE` `_tablename_` ALTER COLUMN `_colname_` SET STORAGE `_storagetype_`指定。 `_storagetype_`是`plain`,`external`, `extended`,或者`main`之一。 版本-1 的函數調用風格也令我們可能返回一"套"結果([Section 35.9.9](#calibre_link-929)) 并且實現觸發器函數([Chapter 36](#calibre_link-460))和過程語言調用處理器 ([Chapter 51](#calibre_link-646))。 版本-1的代碼也更容易移植,因為它沒有違反C標準對函數調用協議的限制。 更多的細節請參閱源程序中的`src/backend/utils/fmgr/README`文件。 ## 35.9.5\. 書寫代碼 在轉到更深的話題之前,先要討論一些PostgreSQL C語言函數的編碼規則。 雖然可以用C以外的其它語言書寫用于 PostgreSQL的共享函數, 但通常都很麻煩(當它可能的時候),因為其他語言, 比如C++, FORTRAN或者Pascal并不遵循C的調用習慣。 也就是說,其它語言在函數之間的傳遞參數和返回值的方式不一樣。 因此假設你的C-編程語言函數是用C寫的。 書寫和編譯C函數的基本規則如下: * 使用`pg_config --includedir-server` 找出PostgreSQL服務器的頭文件安裝在你的系統上的 (或者你的用戶正在運行)的位置。 * 把你的代碼編譯成可以動態裝入PostgreSQL 的庫文件總是需要一些特殊的標記。參閱[Section 35.9.6](#calibre_link-926)獲取如何在你的平臺上做這件事的詳細說明。 * 按照[Section 35.9.1](#calibre_link-921)的指示為你的共享庫定義一個"magic block"。 * 當分配內存時,用PostgreSQL的`palloc`和 `pfree`函數取代相應的C庫函數 `malloc`和`free`。 用`palloc`分配的內存在每個事務結束時會自動釋放,避免了內存泄露。 * 使用`memset`(或者在第一個位置分配`palloc0`)的你的結構字節總是零。 即使你給結構分配每個字段,可能有對齊填充(結構中含有孔)包含垃圾值。如果沒有這一點, 很難支持散列索引和哈希連接,你必須只挑出你的數據結構中重要的位來計算一個散列。 規劃器有時也依賴于通過位平等比較常數,所以如果邏輯等效值不是按位平等, 則你可能得到不良的規劃結果。 * 大多數的PostgreSQL內部類型定義在`postgres.h`中, 而函數管理器接口(`PG_FUNCTION_ARGS`等等)都在`fmgr.h`中, 所以你至少應該包括這兩個文件。出于移植性原因, 最好_先_包括`postgres.h`再包含其它系統或者用戶頭文件。 包含`postgres.h`將自動包含`elog.h`和`palloc.h`。 * 在目標文件里定義的符號一定不能相互沖突, 也不能和定義在PostgreSQL服務器可執行代碼中的符號名字沖突。 如果你看到了與此相關的錯誤信息,那么必須重命名你的函數或者變量。 ## 35.9.6\. 編譯和鏈接動態加載的函數 在能夠使用由 C 寫的PostgreSQL擴展函數之前, 必須用一種特殊的方法編譯和鏈接它們,這樣才能生成可以被服務器動態加載的文件。 準確地說是需要創建一個_共享庫_。 如果需要更多信息,那么你應該閱讀操作系統的文檔,特別是 C 編譯器(`cc`) 和連接器(`ld`)的文檔。另外,PostgreSQL 源代碼里包含幾個可以運行的例子,它們在`contrib`目錄里。不過, 如果你依賴這些例子,那么你的模塊將依賴于PostgreSQL源代碼的可用性。 創建共享庫和鏈接可執行文件類似:首先把源代碼編譯成目標文件,然后把目標文件鏈接起來。 目標文件需要創建成_位置無關碼_(PIC), 也就是在可執行程序加載它們的時候, 它們可以被放在可執行程序內存里的任何地方(用于可執行文件的目標文件通常不是用這個方式編譯的), 鏈接動態庫的命令包含特殊標志,與鏈接可執行文件的命令是有區別的(至少理論上如此,不過現實未必)。 在下面的例子里,假設你的源程序代碼在`foo.c`文件里,并且我們要創建 `foo.so`的共享庫。中介的對象文件將叫做`foo.o` (除非另外注明)。雖然一個共享庫可以包含多個對象文件,但是在這里只用一個。 FreeBSD 創建PIC的編譯器標志是`-fpic`。創建共享庫的鏈接器標志是`-shared`。 ``` gcc -fpic -c foo.c gcc -shared -o foo.so foo.o ``` 上面方法適用于 3.0 版本的FreeBSD。 HP-UX 創建PIC的編譯器標志是`+z` 。如果使用GCC 則是`-fpic`。創建共享庫的鏈接器標志是`-b`。因此: ``` cc +z -c foo.c ``` 或: ``` gcc -fpic -c foo.c ``` 然后: ``` ld -b -o foo.sl foo.o ``` HP-UX使用`.sl`作為共享庫擴展名,和其它大部分系統不同。 IRIX PIC是缺省,不需要使用特殊的編譯器選項。創建共享庫的鏈接器標志是`-shared`。 ``` cc -c foo.c ld -shared -o foo.so foo.o ``` Linux 創建PIC的編譯器標志是`-fpic`。在某些平臺上如果`-fpic` 不工作則必須使用`-fPIC`。 參考 GCC 手冊獲取更多信息。創建共享庫的編譯器標志是`-shared`。一個完整的例子看起來像: ``` cc -fpic -c foo.c cc -shared -o foo.so foo.o ``` Mac OS X 這里是一個例子。假設開發工具已經安裝好了。 ``` cc -c foo.c cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o ``` NetBSD 創建PIC的編譯器標志是`-fpic`。對于ELF系統, 帶`-shared`標志的編譯命令用于鏈接共享庫。在老的非 ELF 系統里, 則使用`ld -Bshareable`。 ``` gcc -fpic -c foo.c gcc -shared -o foo.so foo.o ``` OpenBSD 創建PIC的編譯器標志是`-fpic`。而 `ld -Bshareable`用于鏈接共享庫。 ``` gcc -fpic -c foo.c ld -Bshareable -o foo.so foo.o ``` Solaris 用 Sun 編譯器時創建PIC的編譯器標志是`-KPIC`; 用GCC編譯器時創建PIC的編譯器標志是`-fpic`。 鏈接共享庫時兩個編譯器都可以用`-G`,此外GCC還可以用`-shared`。 ``` cc -KPIC -c foo.c cc -G -o foo.so foo.o ``` 或 ``` gcc -fpic -c foo.c gcc -G -o foo.so foo.o ``` Tru64 UNIX PIC是缺省,不需要使用特殊的編譯器選項。帶特殊選項的`ld`用于鏈接: ``` cc -c foo.c ld -shared -expect_unresolved '*' -o foo.so foo.o ``` 用 GCC 代替系統編譯器時的過程是一樣的;不需要特殊的選項。 UnixWare 用 SCO 編譯器時創建PIC的編譯器標志是`-K PIC`; 用GCC編譯器時創建PIC的編譯器標志是 `-fpic`。鏈接共享庫時 SCO 編譯器用`-G`而GCC 使用`-shared`。 ``` cc -K PIC -c foo.c cc -G -o foo.so foo.o ``` 或 ``` gcc -fpic -c foo.c gcc -shared -o foo.so foo.o ``` > **Tip:** 如果你覺得這些步驟實在太復雜,那么你應該考慮使用[GNU Libtool](http://www.gnu.org/software/libtool/),它把平臺的差異隱藏在了一個統一的接口里。 生成的共享庫文件然后就可以加載到PostgreSQL里面去了。 在給`CREATE FUNCTION`命令聲明文件名的時候, 必須聲明共享庫文件的名字而不是中間目標文件的名字。 請注意你可以在`CREATE FUNCTION`命令上忽略系統標準的共享庫擴展名(通常是 `.so`或`.sl`),并且出于最佳的兼容性考慮也應該忽略。 回頭看看[Section 35.9.1](#calibre_link-921)獲取有關服務器預期在哪里找到共享庫的信息。 ## 35.9.7\. 復合類型參數 復合類型不像 C 結構那樣有固定的布局。復合類型的實例可能包含空(NULL)字段。另外, 一個屬于繼承層次一部分的復合類型可能和同一繼承范疇的其它成員有不同的域/字段。 因此,PostgreSQL提供一個過程接口用于從C中訪問復合類型。 假設為下面查詢寫一個函數: ``` SELECT name, c_overpaid(emp, 1500) AS overpaid FROM emp WHERE name = 'Bill' OR name = 'Sam'; ``` 使用調用約定版本0,可以這樣定義`c_overpaid`: ``` #include "postgres.h" #include "executor/executor.h" /* for GetAttributeByName() */ #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif bool c_overpaid(HeapTupleHeader t, /* the current row of emp */ int32 limit) { bool isnull; int32 salary; salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull)); if (isnull) return false; return salary > limit; } ``` 如果用版本-1則會寫成下面這樣: ``` #include "postgres.h" #include "executor/executor.h" /* for GetAttributeByName() */ #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif PG_FUNCTION_INFO_V1(c_overpaid); Datum c_overpaid(PG_FUNCTION_ARGS) { HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0); int32 limit = PG_GETARG_INT32(1); bool isnull; Datum salary; salary = GetAttributeByName(t, "salary", &isnull); if (isnull) PG_RETURN_BOOL(false); <!-- /* Alternatively, we might prefer to do PG_RETURN_NULL() for null salary. */ --> /*另外,可能更希望將PG_RETURN_NULL()用在null薪水上*/ PG_RETURN_BOOL(DatumGetInt32(salary) > limit); } ``` `GetAttributeByName`是PostgreSQL系統函數, 用來返回當前記錄的字段。它有三個參數:類型為`HeapTupleHeader`的傳入函數的參數、你想要的字段名稱、 一個確定字段是否為 NULL 的返回參數。`GetAttributeByName`函數返回一個 `Datum`值,你可以用對應的`DatumGet`_XXX_`()`宏把它轉換成合適的數據類型。 請注意,如果設置了NULL標志,那么返回值是無意義的,在準備對結果做任何處理之前, 總是要先檢查NULL標志。 還有一個`GetAttributeByNum`用字段編號而不是字段名選取目標字段。 下面的命令在SQL里聲明`c_overpaid`函數: ``` CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean AS '_DIRECTORY_/funcs', 'c_overpaid' LANGUAGE C STRICT; ``` 請注意使用`STRICT`后就不需要檢查輸入參數是否有NULL。 ## 35.9.8\. 返回行(復合類型) 要從一個C語言函數里返回一個行或復合類型的數值, 可以使用一個特殊的API,它提供了許多宏和函數來消除大多數制作復合數據類型的復雜性。 要使用該API,源代碼必須包含: ``` #include "funcapi.h" ``` 制作一個復合類型數據值(也就是一個"行")有兩種方法: 你可以從一個 Datum 值數組里制作,也可以從一個可以傳遞給該行的字段類型的輸入轉換函數的 C 字符串數組里制作。不管是哪種方式,你首先都需要為行結構獲取或者制作一個 `TupleDesc`描述符。在使用 Datums 的時候,你給`BlessTupleDesc`傳遞這個`TupleDesc` 然后為每行調用`heap_form_tuple`。在使用C字符串的時候, 你給`TupleDescGetAttInMetadata` 傳遞`TupleDesc`,然后為每行調用`BuildTupleFromCStrings`。 如果是返回一個行集合的場合,所有設置步驟都可以在第一次調用該函數的時候一次性完成。 有幾個便利函數可以用于設置所需要的`TupleDesc`。 在大多數返回復合類型給調用者的函數里建議的做法是這樣的: ``` TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc) ``` 把傳遞給調用函數自己的`fcinfo`傳遞給它(要求使用版本-1 的調用習慣)。 `resultTypeId`可以聲明為`NULL`或者 接收函數的結果類型OID的局部變量地址(指針)。 `resultTupleDesc`應該是一個局部的`TupleDesc`變量地址(指針)。 檢查結果是否`TYPEFUNC_COMPOSITE`;如是, `resultTupleDesc`就已經填充好需要的`TupleDesc`了。 如果不是,你可以報告一個類似"返回記錄的函數在一個不接受記錄的環境中被調用"的錯誤。 > **Tip:** `get_call_result_type`可以把一個多態的函數結果解析為實際類型; 因此它在返回多態的標量結果的函數里也很有用,而不僅僅是返回復合類型的函數里。 `resultTypeId`輸出主要用于那些返回多態的標量類型的函數。 > **Note:** `get_call_result_type`有一個同胞弟兄`get_expr_result_type` 可以用于給一個用表達式樹表示的函數調用解析輸出, 它可以用于視圖從函數本身外邊判斷結果類型的場合。 還有一個`get_func_result_type`可以用在只能拿到函數OID的場合。 不過,這些函數不能處理那些聲明為返回`record`的函數, 并且`get_func_result_type`不能解析多態的類型, 因此你最好還是使用`get_call_result_type`。 舊的,現在已經廢棄的獲取`TupleDesc`的函數是: ``` TupleDesc RelationNameGetTupleDesc(const char *relname) ``` 它可以從一個命名的關系里為行類型獲取一個`TupleDesc`,還有: ``` TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases) ``` 可以基于類型 OID 獲取一個`TupleDesc`。 它可以用于給一個基本類型或者一個復合類型獲取`TupleDesc`。 不過它不能處理返回`record`的函數,并且不能解析多態的類型。 一旦你有了一個`TupleDesc`,那么調用: ``` TupleDesc BlessTupleDesc(TupleDesc tupdesc) ``` 如果你想使用Datum,或者: ``` AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc) ``` 如果你想使用C字符串。如果你在寫一個返回集合的函數, 那么你可以把這些函數的結果保存在`FuncCallContext`結構里 (分別使用`tuple_desc`或者`attinmeta`字段)。 在使用Datum的時候,使用: ``` HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull) ``` 制作一個`HeapTuple`,它把數據以Datum的形式交給用戶。 當使用C字符串時,使用: ``` HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) ``` 制作一個`HeapTuple`,以C字符串的形式給出用戶數據。 `values`是一個 C 字符串的數組,返回行的每個字段對應其中一個。 每個 C 字符串都應該是字段數據類型的輸入函數預期的形式。 為了從其中一個字段中返回一個NULL, `values`數組中對應的指針應該設置為`NULL`。 這個函數將會需要為你返回的每個行調用一次。 一旦你制作了一個從你的函數中返回的行,那么該行必須轉換成一個`Datum`。使用: ``` HeapTupleGetDatum(HeapTuple tuple) ``` 把一個`HeapTuple`轉換為一個有效的`Datum`。 如果你想只返回一行,那么這個 Datum 可以用于直接返回, 或者是它可以用作在一個返回集合的函數里的當前返回值。 例子在下面給出。 ## 35.9.9\. 返回集合 還有一個特殊的API用于提供從C語言函數中返回集合(多行)。 一個返回集合的函數必須遵循版本-1的調用方式。 同樣,源代碼必須包含`funcapi.h`,就像上面說的那樣。 一個返回集合的函數(SRF)通常為它返回的每個項都調用一次。 因此SRF必須保存足夠的狀態用于記住它正在做的事情以及在每次調用的時候返回下一個項。 表函數 API 提供了`FuncCallContext`結構用于幫助控制這個過程。 `fcinfo-&gt;flinfo-&gt;fn_extra` 用于保存一個跨越多次調用的指向`FuncCallContext`的指針。 ``` typedef struct { /* * 前面已經被調用的次數 * 初始的時候,call_cntr 被 SRF_FIRSTCALL_INIT() 置為 0, *并且每次你調用 SRF_RETURN_NEXT() 的時候都遞增 */ uint32 call_cntr; /* * 可選的最大調用數量 * 這里的 max_calls 只是為了方便,設置它也是可選的。 * 如果沒有設置,你必須提供可選的方法來知道函數何時結束。 */ uint32 max_calls; /* * 指向結果槽位的可選指針 * 這個數據類型已經過時,只用于向下兼容。也就是那些使用已廢棄的TupleDescGetSlot()的用戶定義 SRF */ TupleTableSlot *slot; /* * 可選的指向用戶提供的雜項環境信息的指針 * user_fctx 用做一個指向你自己的結構的指針,包含任意提供給你的函數的調用間的環境信息 */ void *user_fctx; /* * 可選的指向包含屬性類型輸入元信息的結構數組的指針 * attinmeta 用于在返回行的時候(也就是說返回復合數據類型) * 在只返回基本(也就是標量)數據類型的時候并不需要。 * 只有在你準備用 BuildTupleFromCStrings() 創建返回行的時候才需要它。 */ AttInMetadata *attinmeta; /* * 用于必須在多次調用間存活的結構的內存環境 * multi_call_memory_ctx 是由 SRF_FIRSTCALL_INIT() 為你設置的,并且由 SRF_RETURN_DONE() 用于清理。 * 它是用于存放任何需要跨越多次調用 SRF 之間重復使用的內存。 */ MemoryContext multi_call_memory_ctx; /* * 可選的指針,指向包含行描述的結構 * tuple_desc 用于返回行(也就是說復合數據類型)并且只是在你想使用 heap_form_tuple() 而不是 BuildTupleFromCStrings() 制作行的時候需要。 * 請注意這里存儲的 TupleDesc 指針通常應該先用 BlessTupleDesc() 處理。 */ TupleDesc tuple_desc; } FuncCallContext; ``` 一個SRF使用自動操作`FuncCallContext`結構 (可以通過`fn_extra`找到)的若干個函數和宏。使用: ``` SRF_IS_FIRSTCALL() ``` 來判斷你的函數是第一次調用還是后繼的調用。只有在第一次調用的時候,使用: ``` SRF_FIRSTCALL_INIT() ``` 初始化`FuncCallContext`。在每次函數調用時(包括第一次),使用: ``` SRF_PERCALL_SETUP() ``` 為使用`FuncCallContext`做恰當的設置以及清理任何前面的輪回里面剩下的已返回的數據。 如果你的函數有數據要返回,使用: ``` SRF_RETURN_NEXT(funcctx, result) ``` 返回給調用者(`result`必須是個`Datum`,要么是單個值, 要么是像前面介紹的那樣準備的行)。 最后,如果你的函數結束了數據返回,使用: ``` SRF_RETURN_DONE(funcctx) ``` 清理并結束SRF。 在SRF被調用時的內存環境是一個臨時環境, 在調用之間將會被清理掉。 這意味著你不需要`pfree`所有你`palloc`的東西;它會自動消失的。 不過,如果你想分配任何跨越調用存在的數據結構, 那你就需要把它們放在其它什么地方。 被`multi_call_memory_ctx`引用的環境適合用于保存那些需要直到 SRF結束前都存活的數據。在大多數情況下, 這意味著你在第一次調用設置的時候應該切換到`multi_call_memory_ctx`。 一個完整的偽代碼例子看起來像下面這樣: ``` Datum my_set_returning_function(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; Datum result; _更多的聲明_ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* 這里放出現一次的設置代碼: */ _用戶代碼_ _if 返回復合_ _制作 TupleDesc 以及可能還有 AttInMetadata_ _endif 返回復合_ _用戶定義代碼_ MemoryContextSwitchTo(oldcontext); } /* 每次都執行的設置代碼在這里出現: */ _用戶定義代碼_ funcctx = SRF_PERCALL_SETUP(); _用戶定義代碼_ /* 這里只是用來測試是否完成的一個方法: */ if (funcctx->call_cntr < funcctx->max_calls) { /* 這里想返回另外一個條目: */ _用戶代碼_ _獲取結果_ SRF_RETURN_NEXT(funcctx, result); } else { /* 這里完成返回條目的工作了,只需要清理就OK了: */ _用戶代碼_ SRF_RETURN_DONE(funcctx); } } ``` 一個返回復合類型的完整SRF例子看起來像這樣: ``` PG_FUNCTION_INFO_V1(retcomposite); Datum retcomposite(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tupdesc; AttInMetadata *attinmeta; /* 只是在第一次調用函數的時候干的事情 */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; /*創建一個函數環境,用于在調用間保持住*/ funcctx = SRF_FIRSTCALL_INIT(); /* 切換到適合多次函數調用的內存環境 */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* 要返回的行總數 */ funcctx->max_calls = PG_GETARG_UINT32(0); /* 為了結果類型制作一個行描述 */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); /* * 生成稍后從裸 C 字符串生成行的屬性元數據 */ attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; MemoryContextSwitchTo(oldcontext); } /* 每次函數調用都要做的事情 */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; attinmeta = funcctx->attinmeta; if (call_cntr < max_calls) /* 在還有需要發送的東西時繼續處理 */ { char **values; HeapTuple tuple; Datum result; /* * 準備一個數值數組用于版本的返回行 * 它應該是一個C字符串數組,稍后可以被合適的類型輸入函數處理。 */ values = (char **) palloc(3 * sizeof(char *)); values[0] = (char *) palloc(16 * sizeof(char)); values[1] = (char *) palloc(16 * sizeof(char)); values[2] = (char *) palloc(16 * sizeof(char)); snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1)); snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1)); snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1)); /* 制作一個行 */ tuple = BuildTupleFromCStrings(attinmeta, values); /* 把行做成 datum */ result = HeapTupleGetDatum(tuple); /* 清理(這些實際上并非必要) */ pfree(values[0]); pfree(values[1]); pfree(values[2]); pfree(values); SRF_RETURN_NEXT(funcctx, result); } else /* 在沒有數據殘留的時候干的事情 */ { SRF_RETURN_DONE(funcctx); } } ``` 在 SQL 里聲明這個函數的一個方法是: ``` CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer); CREATE OR REPLACE FUNCTION retcomposite(integer, integer) RETURNS SETOF __retcomposite AS '_filename_', 'retcomposite' LANGUAGE C IMMUTABLE STRICT; ``` 另外一個方法是使用 OUT 參數: ``` CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer, OUT f1 integer, OUT f2 integer, OUT f3 integer) RETURNS SETOF record AS '_filename_', 'retcomposite' LANGUAGE C IMMUTABLE STRICT; ``` 請注意在這個方法里,函數的輸出類型實際上是匿名的`record`類型。 參閱源碼發布包里的[contrib/tablefunc](#calibre_link-401) 獲取更多有關返回集合的函數的例子。 ## 35.9.10\. 多態參數和返回類型 C 語言函數可以聲明為接受和返回多態的類型`anyelement`,`anyarray`, `anynonarray`, `anyenum`和`anyrange`。 參閱[Section 35.2.5](#calibre_link-909)獲取有關多態函數的更詳細解釋。 如果函數參數或者返回類型定義為多態類型, 那么函數的作者就無法預先知道他將收到的參數,以及需要返回的數據。 在`fmgr.h`里有兩個過程,可以讓版本-1 的 C 函數知道它的參數的確切數據類型以及 它需要返回的數據類型。這兩個過程叫`get_fn_expr_rettype(FmgrInfo *flinfo)`和 `get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)`。 它們返回結果或者參數的類型 OID,如果這些信息不可獲取,則返回 InvalidOid 。 結構`flinfo`通常是以`fcinfo-&gt;flinfo`進行訪問的。 參數`argnum`是以 0 為基的。 `get_call_result_type`也可以替代`get_fn_expr_rettype`。 還有`get_fn_expr_variadic`用于找出是否調用包含明確的`VARIADIC`關鍵字。 對于`VARIADIC "any"`函數是最有用的,正如下面所述。 比如,假設想寫一個函數接受任意類型的一個元素,并且返回該類型的一個一維數組: ``` PG_FUNCTION_INFO_V1(make_array); Datum make_array(PG_FUNCTION_ARGS) { ArrayType *result; Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 0); Datum element; bool isnull; int16 typlen; bool typbyval; char typalign; int ndims; int dims[MAXDIM]; int lbs[MAXDIM]; if (!OidIsValid(element_type)) elog(ERROR, "could not determine data type of input"); /* 獲取提供的元素(要小心其為NULL的情況) */ isnull = PG_ARGISNULL(0); if (isnull) element = (Datum) 0; else element = PG_GETARG_DATUM(0); /* 維數是1 */ ndims = 1; /* 有1個元素 */ dims[0] = 1; /* 數組下界是1 */ lbs[0] = 1; /* 獲取有關元素類型需要的信息 */ get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); /* 然后制作數組 */ result = construct_md_array(&element, &isnull, ndims, dims, lbs, element_type, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result); } ``` 下面的命令用SQL聲明`make_array`函數: ``` CREATE FUNCTION make_array(anyelement) RETURNS anyarray AS '_DIRECTORY_/funcs', 'make_array' LANGUAGE C IMMUTABLE; ``` 有一個變種多態性,僅適用于C語言函數:他們可以聲明采取類型 `"any"`的參數。(注意:這個類型名稱必須是雙引號, 因為它同時也是一個SQL的保留字)。類似于`anyelement`除了它并不限制不同`"any"` 參數是相同類型,也沒有幫助確定該函數的結果類型。一個C語言的函數也可以聲明最后的參數為`VARIADIC "any"`。 這將匹配一個或多個任意類型的實參(不一定是相同的類型)。 這些參數_不_被收集到一個數組中如發生正常的可變參數函數; 他們會分別被傳遞到函數中。`PG_NARGS()`宏和 上面描述的方法必須被用來確定實際參數數目 以及使用此功能時的類型。同時,這個函數的用戶可能希望在函數調用中使用`VARIADIC`關鍵字, 以期望函數把數組元素看作單獨的參數。函數本身必須實現 想要的操作,使用`get_fn_expr_variadic`之后 檢測實際參數被標記為`VARIADIC`。 ## 35.9.11\. 轉換函數 一些函數的調用可以在規劃中基于函數的屬性特性被簡化。比如, `int4mul(n, 1)`可簡化為`n`。 為了定義函數-特定優化,寫_transform function_并將其OID放入 基函數的`pg_proc`項的`protransform`字段中, 轉換函數必須有SQL簽名`protransform(internal) RETURNS internal`。 參數,其實`FuncExpr *`是代表調用基函數的一個虛擬節點。 如果表達式樹的變換函數的研究證明簡化的表達式樹可以替代所有 可能的具體調用其表示建立并且返回簡單的表達式。 否則,返回`NULL`指針(_不是_SQL null)。 我們不做任何保證, PostgreSQL不會調用這種情況下的主要函數以簡化轉換函數。 確保在簡化的表達式以及實際調用主要函數之間的嚴格等價性。 當前,這個設施在SQL水平上不暴露給用戶,出于安全考慮。因此只有實踐中用于優化內置函數。 ## 35.9.12\. 共享內存和LWLocks 插件可能保留 LWLocks 并在服務器啟動時分配共享內存。 插件的共享庫必須通過指定[shared_preload_libraries](#calibre_link-576)的方法預先加載。 ``` void RequestAddinShmemSpace(int size) ``` 共享內存可以通過在`_PG_init`函數中調用。 LWLocks通過調用進行預留: ``` void RequestAddinLWLocks(int n) ``` 來自`_PG_init`。 為了避免可能的競爭條件,當連接并且初始化共享內存分配時, 每個后端應該使用LWLock `AddinShmemInitLock`,如下所示: ``` static mystruct *ptr = NULL; if (!ptr) { bool found; LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); ptr = ShmemInitStruct("my struct name", size, &found); if (!found) { initialize contents of shmem area; acquire any requested LWLocks using: ptr->mylockid = LWLockAssign(); } LWLockRelease(AddinShmemInitLock); } ``` ## 35.9.13\. 使用C++的可擴展性 盡管PostgreSQL后端以C寫入,如果伴隨這些準則, 在C++中寫入擴展是可能的: * 所有被后端訪問的函數必須提供到后端的C接口; 這些C函數然后調用C++函數。比如,`extern C`聯系 * 使用合適的存儲單元分配方法釋放內存。比如,使用`palloc()`分配大部分后端內存, 因此使用`pfree()`釋放它。在這種情況下使用C++ `delete`將失敗。 * 防止異常傳播到C代碼(使用捕獲所有`extern C`函數的最高水平上的塊)。 即使是C++代碼沒有明確地拋出異常,這是必要的,由于事件比如內存不足仍然可以拋出異常。 任何異常必須被捕獲,并且將適當的錯誤傳遞給C接口。如果可能的話,編譯C++ `-fno-exceptions`以完全消除異常;在這樣的案例中, 你必須檢查你的C++代碼的錯誤,比如檢查通過`new()`返回的NULL。 * 如果從C++代碼中調用后端函數,確保C++調用堆棧中只包含純舊的數據結構(POD)。 這是必要的因為后端錯誤產生一個遙遠的`longjmp()`,不適當的展開與非-POD對象的C++ 調用堆棧。 總之,把C++代碼放在與后端接口的`extern C`函數之后是最好的, 并且避免異常,內存以及調用堆棧泄露。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看