文章目录
Writing Postgres Extensions - the Basics
-
first article
-
about
-
extending Pg through extensions.
-
the code examples here on branch part_i
-
https://github.com/adjust/postgresql_extension_demo/tree/part_i
base36
- You might already know the trick used by url shorteners.
- Use some unique random characters such as http://goo.gl/EAZSKW to point to something else.
- You have to remember what points to where, of course, so you need to store it in a database.
- But instead of saving 6 characters using varchar(6) (and thus wasting 7 bytes)
- why not use an integer with 4 bytes and
- represent it as base36?
The Extension Skeleton
- To be able to run the CREATE EXTENSION ,
- your extension needs at least two files
- control file in the format extension_name.control,
- tell Pg some basics about your extension
- a extension’s SQL script in the format extension–version.sql.
- add them into our project directory.
- our control file might be:
- base36.control
# base36 extension
comment = 'base36 datatype'
default_version = '0.0.1'
relocatable = true
- extension no functionality.
- add some in an SQL script file:
- base36–0.0.1.sql
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION base36" to load this file. \quit
CREATE FUNCTION base36_encode(digits int)
RETURNS text
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
chars char[];
ret varchar;
val int;
BEGIN
chars := ARRAY[
'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h',
'i','j','k','l','m','n','o','p','q','r','s','t', 'u','v','w','x','y','z'
];
val := digits;
ret := '';
WHILE val != 0 LOOP
ret := chars[(val % 36)+1] || ret;
val := val / 36;
END LOOP;
RETURN(ret);
END;
$$;
- second line ensures the file won’t be loaded into the database directly,only via CREATE EXTENSION.
- 这个简单的pl/pgsql函数允许将任何整数编码到它的base36表示形式中。
- 两文件复制到pg的SHAREDIR/extension中(可通过pg_config命令获取),就可通过CREATE EXTENSION使用这个扩展
- 但我们不会麻烦用户去弄清楚这些文件放在哪里,以及如何手动复制它们,这是makefile该做的。
- 现在项目中添加一个makefile。
Makefile
- 9.1开始,每个PostgreSQL安装都为扩展提供了一个名为PGXS的构建基础设施,允许在已经安装的服务器上轻松构建扩展。
- 构建扩展所需的大多环境变量都在pg_config中设置,可简单重用。
- 对于我们的示例,下面这个Makefile就符合我们的需求。
EXTENSION = base36 # 扩展名称
DATA = base36--0.0.1.sql # 要安装的脚本文件
# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
- 现在可开始用扩展了
- 在你的工程运行make install
- 并在数据库中执行如下
test=# CREATE EXTENSION base36;
CREATE EXTENSION
Time: 3,329 ms
test=# SELECT base36_encode(123456789);
base36_encode
---------------
21i3v9
(1 row)
Time: 0,558 ms
编写测试
-
易向项目中加些回归测试,
- 这些测试可在完成make install之后
- 通过make install check调
-
可将测试脚本文件放sql/下
-
对每个测试文件,expected/中也有一个对应包含预期输出的文件,该文件有与测试脚本相同的名称,.out
-
make install check执行每个测试,
- 并将结果输出与匹配的预期文件比较。
-
任何差异都将写入文件regression.diffs
-
sql/base36_test.sql
CREATE EXTENSION base36;
SELECT base36_encode(0);
SELECT base36_encode(1);
SELECT base36_encode(10);
SELECT base36_encode(35);
SELECT base36_encode(36);
SELECT base36_encode(123456789);
- 还需告诉Makefile关于测试的信息(第3行):
- 文件名:Makefile
EXTENSION = base36
DATA = base36--0.0.1.sql
REGRESS = base36_test # 我们的测试脚本文件(没有后缀名)
# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
优化速度,写一些C代码
- 在扩展中提供相关功能是共享代码的一种方法,
- 但真正有趣的是用c实现。
- 让我们获得第一个1M base36数字。
test=# SELECT i, base36_encode(i) FROM generate_series(1,1e6::int) i;
Time: 11289,610 ms
- base36.c
#include "postgres.h"
#include "fmgr.h"
#include "utils/builtins.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(base36_encode);
Datum
base36_encode(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
char base36[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
/* max 6 char + '\0' */
char *buffer = palloc(7 * sizeof(char));
unsigned int offset = sizeof(buffer);
buffer[--offset] = '\0';
do {
buffer[--offset] = base36[arg % 36];
} while (arg /= 36);
PG_RETURN_TEXT_P(cstring_to_text(&buffer[offset]));
}
- #include "postgres.h"包括与Postgres接口所需的大部分基本内容。 须含在声明Postgres函数的每个C文件中。
- #include "fmgr.h"以使用PG_GETARG_XXX和PG_RETURN_XXX宏。
- #include "utils/builtins.h"在Postgres的内置数据类型上定义了一些操作(稍后使用cstring_to_text)
- PG_MODULE_MAGIC 是8.2中包含头文件fmgr.h后,
- 模块源文件中的一个(且仅一个)中需要的魔法块。
- PG_FUNCTION_INFO_V1(base36_encode);
- 将该函数作为版本1调用约定引入Postges,
- 只有在希望用到函数->Postgres接口时才需要。
- Dtum是每个Postgres函数的返回类型,可是任何数据类型。
- 可把它想象成类似于void *
- base36_encode(PG_FUNCTION_ARGS) 我们的函数名
- PG_FUNCTION_ARGS可接受任何数字和任何类型的参数。
- int32 arg = PG_GETARG_INT32(0);
- 获取第一个参数,编号从0开始。
- 须用fmgr.h中定义的PG GETARG XXX宏来获取实际的参数值。
- har *buffer = palloc(7 * sizeof(char));
- 为了在分配内存时防止内存泄漏,总使用palloc和pfree,而不是C库函数malloc和free。
- palloc分配的内存将在每个事务结束时自动释放。
- 你也可以使用palloc0来确保字节清零。
- PG_RETURN_TEXT_P(cstring_to_text(&buffer[offset]));
- 要将一个值返回给Postgres,
- 须使用一个PG_RETURN_XXX宏。
- cstring_to_text将cstring转换为Postgres文本类型。
- 完成c语言代码部分之后,需要修改SQL函数。
- 文件名:base36-0.0.1.sql
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION base36" to load this file. \quit
CREATE FUNCTION base36_encode(integer) RETURNS text
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
- 为能够用该函数,还要改Makefile(第4行)
- 文件名:Makefile
EXTENSION = base36 # 扩展名称
DATA = base36--0.0.1.sql # 要安装的脚本文件
REGRESS = base36_test # 测试脚本文件 (没有后缀名)
MODULES = base36 # 要构建的c模块文件
# postgres build stuff
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
- make install && make install check进行测试。
- 打开数据库控制台证明速度要快(30倍)
test=# SELECT i, base36_encode(i) FROM generate_series(1,1e6::int) i;
Time: 361,054 ms