PostgreSql擴展Sql-動態加載共享庫(C函數)


  • 基於 psql (PostgreSQL) 10.4

pg_language表定義了函數實現所使用的語言。主要支持了C語言和SQL語句。一些可選的語言包括pl/pgsql、tcl和perl。

ligang=# select lanname, lanispl, lanpltrusted, lanplcallfoid, laninline, lanvalidator from pg_language;
 lanname  | lanispl | lanpltrusted | lanplcallfoid | laninline | lanvalidator 
----------+---------+--------------+---------------+-----------+--------------
 internal | f       | f            |             0 |         0 |         2246
 c        | f       | f            |             0 |         0 |         2247
 sql      | f       | t            |             0 |         0 |         2248
 plpgsql  | t       | t            |         13198 |     13199 |        13200

pg_proc表對函數進行了定義。每一個函數在該表中都對應一個元組,包含函數名。輸入參數類型,返回類型以及對函數的定義(可能是文本,可能是一段編譯型語句,也可能是對可執行代碼的引用)。編譯過的函數可以靜態地鏈接到服務器上,或者在存儲在共享庫內,當第一次使用該庫時動態的載入。

ligang=# select proname,prolang, prorettype,proargtypes, prosrc,probin from pg_proc where proname like '%square%';
 proname | prolang | prorettype | proargtypes |           prosrc           |       probin       
---------+---------+------------+-------------+----------------------------+--------------------
 square  |   13201 |         23 | 23          | begin return $1 * $1; end; | 
 squares |      13 |         23 | 23          | squares_return_int         | $libdir/squares.so

查看其數據類型

ligang=# select oid , typname from pg_type where oid = 23;
 oid | typname 
-----+---------
  23 | int4
(1 row)

以下是示例函數:

C: 與內建SQL類型等效的C類型

int
square_int (int x)
{
	return x * x;
}

把上面的函數編譯成共享庫文件,這樣聲明:

CREATE FUNCTION square(int) RETURNS int
AS '/path/to/square.so', 'square_int'
LANGUAGE 'C';

PL/PGSQL:

ligang=# create function square(int) returns int as 'begin return $1 * $1; end;' LANGUAGE 'plpgsql';
CREATE FUNCTION
ligang=# 
ligang=# 
ligang=# select square(4);
 square 
--------
     16

建立用戶函數動態庫

  • 新建代碼
    #include "postgres.h"
    #include "fmgr.h"

    int 
    square_int(int x)
    {
        return x * x;
    }
    
  • 編譯 - 添加共享庫

    [ligang@yfslcentos71 include]$ gcc -I`pg_config --includedir-server` -c squares.c 
    [ligang@yfslcentos71 include]$ gcc -shared squares.o -o squares.so 
    [ligang@yfslcentos71 include]$ cp squares.so `pg_config --libdir`/
    
  • Pg數據庫裝載
    ligang=# create function squares(int) returns int as '$libdir/squares.so', 'square_int' LANGUAGE 'c' STRICT;

關於PG_MODULE_MAGIC

為了確保不會錯誤加載共享庫文件,從PostgreSQL 開始將檢查那個文件的"magic block",這允許服務器以檢查明顯的不兼容性。

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

如果不打算兼容8.2 PostgreSQL之前的版本, #ifdef測試也可以省略

源碼修改為:

#include "postgres.h" 
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

int 
square_int(int x)
{
    return x * x;
}

版本約定

版本0約定

版本-0方法中,此風格 C 函數的參數和結果用普通 C 風格聲明, 但是要小心使用上面顯示的 SQL 數據類型的 C 表現形式。 (以前版本;)

#include "postgres.h" 
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

int 
square_int(int x)
{
    return x * x;
}

版本1約定 (應當使用該版本)

版本-1調用約定使用宏消除大多數傳遞參數和結果的復雜性。版本-1風格函數的C定義總是下面這樣:

Datum funcname(PG_FUNCTION_ARGS);

另外,宏調用:

PG_FUNCTION_INFO_V1(funcname);

也必須出現在同一個源文件里(通常就可以寫在函數自身前面)。 對那些internal語言函數而言,不需要調用這個宏, 因為PostgreSQL目前假設內部函數都是版本-1。不過,對於動態加載的函數, 它是必須的。

每個實際參數都是用一個對應該參數的數據類型的 PG_GETARG_xxx()宏抓取的, 用返回類型的PG_RETURN_xxx()宏返回結果。 PG_GETARG_xxx()接受要抓取的函數參數的編號 (從 0 開始)作為其參數。PG_RETURN_xxx() 接受實際要返回的數值為自身的參數。

關於PG_GETARG_XXX 定義於 src/include/fmgr.h

/* Macros for fetching arguments of standard types */

#define PG_GETARG_DATUM(n)	 (fcinfo->arg[n])
#define PG_GETARG_INT32(n)	 DatumGetInt32(PG_GETARG_DATUM(n))
#define PG_GETARG_UINT32(n)  DatumGetUInt32(PG_GETARG_DATUM(n))
#define PG_GETARG_INT16(n)	 DatumGetInt16(PG_GETARG_DATUM(n))
#define PG_GETARG_UINT16(n)  DatumGetUInt16(PG_GETARG_DATUM(n))
#define PG_GETARG_CHAR(n)	 DatumGetChar(PG_GETARG_DATUM(n))
#define PG_GETARG_BOOL(n)	 DatumGetBool(PG_GETARG_DATUM(n))
#define PG_GETARG_OID(n)	 DatumGetObjectId(PG_GETARG_DATUM(n))
#define PG_GETARG_POINTER(n) DatumGetPointer(PG_GETARG_DATUM(n))
#define PG_GETARG_CSTRING(n) DatumGetCString(PG_GETARG_DATUM(n))
#define PG_GETARG_NAME(n)	 DatumGetName(PG_GETARG_DATUM(n))
/* these macros hide the pass-by-reference-ness of the datatype: */
#define PG_GETARG_FLOAT4(n)  DatumGetFloat4(PG_GETARG_DATUM(n))
#define PG_GETARG_FLOAT8(n)  DatumGetFloat8(PG_GETARG_DATUM(n))
#define PG_GETARG_INT64(n)	 DatumGetInt64(PG_GETARG_DATUM(n))
/* use this if you want the raw, possibly-toasted input datum: */
#define PG_GETARG_RAW_VARLENA_P(n)	((struct varlena *) PG_GETARG_POINTER(n))
/* use this if you want the input datum de-toasted: */
#define PG_GETARG_VARLENA_P(n) PG_DETOAST_DATUM(PG_GETARG_DATUM(n))
/* and this if you can handle 1-byte-header datums: */
#define PG_GETARG_VARLENA_PP(n) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(n))

代碼

#include "postgres.h" 
#include "fmgr.h"


#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(squares_return_int);

Datum squares_return_int(PG_FUNCTION_ARGS)
{
    int32 arg = PG_GETARG_INT32(0);
    PG_RETURN_INT32(arg * arg);
}

編譯

[ligang@yfslcentos71 include]$ gcc -I`pg_config --includedir-server` -c squares.c 
[ligang@yfslcentos71 include]$ gcc -shared squares.o -o squares.so 
/usr/bin/ld: squares.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
squares.o: could not read symbols: Bad value

[ligang@yfslcentos71 include]$ gcc  -I`pg_config --includedir-server` -fPIC  -c squares.c
[ligang@yfslcentos71 include]$ gcc -shared squares.o -o squares.so
[ligang@yfslcentos71 include]$ 
[ligang@yfslcentos71 include]$ cp squares.so `pg_config --libdir`/

SQL聲明函數

ligang=# create function squares(int) returns int as '$libdir/squares.so', 'squares_return_int' LANGUAGE 'c'  STRICT; 
CREATE FUNCTION

補充

  • 函數聲明為"strict"(嚴格),意思是說如果任何輸入值為NULL, 那么系統應該自動假設一個NULL的結果。這樣處理可以讓我們避免在函數代碼里面檢查 NULL輸入。如果不這樣處理,我們就得明確檢查NULL, 比如為每個傳遞引用的參數檢查空指針。對於傳值類型的參數,我們甚至沒有辦法檢查!

參考Postgresql 9.4手冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM