PostgreSQL 插件编写

PostgreSQL 标榜自己为最先进 (Most Advance) 的开源关系数据库,它支持大部分 SQL 标准并且提供了许多其他现代特性:复杂查询、外键、触发器、视图、事务完整性、MVCC。同样,PostgreSQL 可以用许多方法扩展,比如,通过增加新的数据类型、函数、操作符、聚集函数、索引。PostgreSQL 被设计为易于扩展,因此通过插件我们可以很容易的扩展 PostgreSQL 数据库。本文就从编写一个简单的斐波那契的数据库扩展来介绍 PostgreSQL 插件的编写。

斐波那契数列

斐波那契数列 (Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契 (Leonardoda Fibonacci) 以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、…… 在数学上,斐波纳契数列以如下被以递推的方法定义1

1
F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2) (n >= 2, n ∈ N)

PostgreSQL 扩展插件的框架

我们为了使 PostgreSQL 可以通过 CREATE EXTENSION 命令加载插件,我们的扩展插件至少需要两个文件:一个名为 extension_name.control 的控制文件和一个名为 extension--version.sql 的扩展 SQL 脚本文件。其中控制文件中包含了扩展插件名、版本等基本信息。现在让我们创建这两个文件。

我们的 fibonacci 扩展插件的控制文件 (fibonacci.control) 内容如下:

1
2
3
4
# fibonacci extension
comment = 'fibonacci extension'
default_version = '0.0.1'
relocatable = true

我们将使用 PL/pgSQL 来实现这个插件 (fibonacci–0.0.1.sql),其内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION fibonacci" to load this file. \quit
CREATE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
counter INTEGER := 0;
i INTEGER := 0;
j INTEGER := 1;
BEGIN

IF n < 1 THEN
RETURN 0;
END IF;

WHILE counter < n LOOP
counter := counter + 1;
SELECT j, i + j INTO i, j;
END LOOP;

RETURN i;
END;
$$;

自 PostgreSQL 9.1 之后,PostgreSQL 提供了 PGXS 用于构建其插件,大部分构建插件的环境变量都可以通过 pg_config 得到重用。接下来我们为 fibonacci 插件添加一个 Makefile 文件用于安装插件。

1
2
3
4
5
6
7
EXTENSION = fibonacci        # the extensions name
DATA = fibonacci--0.0.1.sql # script files to install

# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

接下来我们便可以通过 make install 来安装插件。最后,我们在数据库中执行如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
postgres@database:~/postgresql-10.4/fib$ psql postgres
psql (10.4)
Type "help" for help.

postgres=# create extension fibonacci ;
CREATE EXTENSION
postgres=# select fibonacci(10);
fibonacci
-----------
55
(1 row)

postgres=#

测试用例

现在我们完成了基本的插件功能,但是任何程序都应该包含测试用例以便验证程序的正确性。我们可以很方便的为 PostgreSQL 添加回归测试,在执行完 make install 命令之后利用 make installcheck 运行插件的回归测试。PostgreSQL 将回归测试脚本放在插件目录的 sql/ 文件中,每个测试文件都对应一个期望结果的输出文件并放置在插件目录的 expected/ 文件中,测试脚本与期望结果具有相同的名字,唯一不同的是期望结果的文件的后缀名为 .out。命令 make installcheck 执行 psql 目录下的所有脚本并且将输出的结果与 expected 目录中的文件进行比较。任何不同的结果都将被写入 regression.diffs 文件。现在,我们为插件添加测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
postgres@database:~/postgresql-10.4/fib$ cat sql/fibonacci_test.sql
CREATE EXTENSION fibonacci;
SELECT fibonacci(0);
SELECT fibonacci(1);
SELECT fibonacci(2);
SELECT fibonacci(3);
SELECT fibonacci(4);
SELECT fibonacci(5);
SELECT fibonacci(6);
SELECT fibonacci(7);
SELECT fibonacci(8);
SELECT fibonacci(9);
SELECT fibonacci(10);
SELECT fibonacci(11);
SELECT fibonacci(12);
SELECT fibonacci(13);
SELECT fibonacci(14);
SELECT fibonacci(15);
SELECT fibonacci(16);
SELECT fibonacci(17);
SELECT fibonacci(18);
SELECT fibonacci(19);
SELECT fibonacci(20);

同时,我们需要修改 Makefile 文件,以便其能执行回归测试。

1
2
3
4
5
6
7
8
EXTENSION = fibonacci        # the extensions name
DATA = fibonacci--0.0.1.sql # script files to install
REGRESS = fibonacci_test # our test script file (without extension)

# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

现在,我们执行 make install && make installcheck 时,回归测试将会失败,这是由于我们还没有指定期望的输出结果。但是,我们发现目录下多了一个 results 目录,并且该目录下包含 fibonacci_test.outfibonacci_test.out.diff 两个文件。为了简便,我们在这里创建一个 expected 目录,并将 fibonacci_test.out 拷贝到该目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
postgres@database:~/postgresql-10.4/fib$ mkdir expected
postgres@database:~/postgresql-10.4/fib$ mv results/fibonacci_test.out expected/
postgres@database:~/postgresql-10.4/fib$ make installcheck
/home/postgres/postgresql-10.4/lib/pgxs/src/makefiles/../../src/test/regress/pg_regress --inputdir=./ --bindir='/home/postgres/postgresql-10.4/bin' --dbname=contrib_regression fibonacci_test
(using postmaster on Unix socket, default port)
============== dropping database "contrib_regression" ==============
DROP DATABASE
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
============== running regression test queries ==============
test fibonacci_test ... ok

=====================
All 1 tests passed.
=====================

postgres@database:~/postgresql-10.4/fib$

我们测试求 [1,30] 的斐波那契数列,其运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
postgres=# SELECT i, fibonacci(i) FROM generate_series(1,30) i;
i | fibonacci
----+-----------
1 | 1
2 | 1
3 | 2
4 | 3
5 | 5
6 | 8
7 | 13
8 | 21
9 | 34
10 | 55
11 | 89
12 | 144
13 | 233
14 | 377
15 | 610
16 | 987
17 | 1597
18 | 2584
19 | 4181
20 | 6765
21 | 10946
22 | 17711
23 | 28657
24 | 46368
25 | 75025
26 | 121393
27 | 196418
28 | 317811
29 | 514229
30 | 832040
(30 rows)

Time: 12.432 ms
postgres=#

利用 C 语言优化性能

上面我们使用 PL/pgSQL 为 PostgreSQL 编写了斐波那契插件,从上面的运行时间看基本还算可以。但是,我们还可以通过 C 语言来重写这个插件从而提高其性能。下面给出了 C 语言版的斐波那契插件的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*----------------------------------------------------------------------------
*
* fibonacci.c
* Fibonacci extension for PostgreSQL.
*
*----------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"

PG_MODULE_MAGIC;

Datum fibonacci(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(fibonacci);

Datum
fibonacci(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
int32 i = 0;
int32 j = 1;

if (arg < 1) {
PG_RETURN_INT32(0);
}

while (arg--) {
int32 t = j;
j = i + j;
i = t;
}

PG_RETURN_INT32(i);
}
  • postgres.h 文件包含了 PostgreSQL 的一些基本的编程接口,该文件需要在每个声明 Postgres 函数的 C 源文件中出现。
  • fmgr.h 包含了 PG_GETARG_XXXPG_RETURN_XXX 的一系列宏定义。
  • PG_MODULE_MAGIC 是一个魔术块,它用于确保将动态加载的目标文件加载到兼容的服务器中。

现在,我们利用 C 语言重写了斐波那契插件;接着,我们需要修改安装脚本文件 fibonacci--0.0.1.sqlMakefile 文件。为了便于区分,我们将 C 语言实现的版本命名为 0.0.2,因此我们创建一个 fibonacci--0.0.2.sql 的安装脚本,其内容如下:

1
2
3
4
5
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION fibonacci" to load this file. \quit
CREATE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER AS '$libdir/fibonacci'
LANGUAGE C IMMUTABLE STRICT;

接下来修改 Makefile 文件使其能正常编译我们的 fibonacci 插件的源文件。

1
2
3
4
5
6
7
8
9
EXTENSION = fibonacci        # the extensions name
DATA = fibonacci--0.0.2.sql # script files to install
REGRESS = fibonacci_test # our test script file (without extension)
MODULES = fibonacci

# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

注意,我们还需要修改 fibonacci.control 文件中的 default_version0.0.2。现在我们便可以通过 make install && make installcheck 来安装插件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
postgres=# SELECT i, fibonacci(i) FROM generate_series(1,30) i;
i | fibonacci
----+-----------
1 | 1
2 | 1
3 | 2
4 | 3
5 | 5
6 | 8
7 | 13
8 | 21
9 | 34
10 | 55
11 | 89
12 | 144
13 | 233
14 | 377
15 | 610
16 | 987
17 | 1597
18 | 2584
19 | 4181
20 | 6765
21 | 10946
22 | 17711
23 | 28657
24 | 46368
25 | 75025
26 | 121393
27 | 196418
28 | 317811
29 | 514229
30 | 832040
(30 rows)

Time: 2.280 ms
postgres=#

从运行时间上看,C 语言版的斐波那契要比 PL/pgSQL 的运行速度快大约 80%。

备注: 由于 C 语言插件在 PostgreSQL 中采用动态库的形式载入的,因此我们需要修改配置文件中的 shared_preload_libraries 参数。

总结

本文介绍了如何使用 plpgsql 编写 PostgreSQL 的扩展插件,PostgreSQL 有两个基本的文件:

  • extension.control - 插件控制文件
  • extension–version.sql - 用于创建扩展的脚本文件

此外,我们可能还需要 sqlexpected 目录用于存放回归测试用例及其结果。最后,为了提高插件的运行效率,我们将原来的 PL/pgSQL 版本采用 C 语言来实现。

参考

[1] 斐波那契数列
[2] Writing Postgres Extensions - the Basics
[3] PostgreSQL - C-Language Functions