在关于编写Postgres扩展的系列文章的最后四篇文章中,咱们了解了基本的类型和操做符,介绍了调试器并完成了测试套件。git
如今让咱们添加另外一种类型,看看如何在代码库增加时组织代码库。github
你能够在github分支上找到最后一篇帖子的代码库part_iv今天的分支能够在分支part_v上找到sql
咱们可能对咱们的扩展感到满意并在生产中使用它一段时间没有任何问题。如今咱们的业务成功了,int
的范围可能已经不够了。 这意味着咱们须要另外一个基于bigint
的类型bigbase36
,最多能够包含13个字符。shell
这里的问题是咱们不能简单地删除扩展并从新安装新版本。数据库
test=# drop extension base36 ; ERROR: cannot drop extension base36 because other objects depend on it DETAIL: table important_data column token depends on type base36 HINT: Use DROP ... CASCADE to drop the dependent objects too.
若是咱们在这里DROP ... CASCADE
,咱们全部的数据都会丢失。 此外,对于TB级数据库而言,转储和从新建立不是一种选择。咱们想要的是ALTER EXTENSION UPDATE TO '0.0.2'
。幸运的是,Postgres内建了扩展的版本控制。请记住咱们定义的base36.control
文件:c#
文件名:base36.control安全
# base36 extension comment = 'base36 datatype' default_version = '0.0.1' relocatable = true
版本“0.0.1”是咱们执行CREATE EXTENSION base36
时使用的默认版本,致使导入base36--0.0.1.sql
脚本文件。 让咱们另建立一个:服务器
cp base36--0.0.1.sql base36--0.0.2.sql
默认是这样子函数
文件名:base36.control工具
# base36 extension comment = 'base36 datatype' default_version = '0.0.2' relocatable = true
构建
make clean && make && make install && make installcheck
获得
... ERROR: could not stat file "/usr/local/Cellar/postgresql/9.4.0/share/postgresql/extension/base36--0.0.2.sql": No such file or directory command failed: "/usr/local/Cellar/postgresql/9.4.0/bin/psql" -X -c "CREATE EXTENSION IF NOT EXISTS \"base36\"" "contrib_regression" make: *** [installcheck] Error 2
嗯,它想使用extension / base36--0.0.2.sql
但没法找到它。
让咱们修复Makefile并告诉Postgres使用——.sql模式下的全部文件。
文件名:Makefile
EXTENSION = base36 # the extensions name DATA = $(wildcard *--*.sql) # script files to install
咱们如今能够在base36--0.0.2.sql
中添加bigbase36类型
文件:base36-0.0.2.sql
-- base36 stuff omitted CREATE FUNCTION bigbase36_in(cstring) RETURNS bigbase36 AS '$libdir/base36' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION bigbase36_out(bigbase36) RETURNS cstring AS '$libdir/base36' LANGUAGE C IMMUTABLE STRICT; CREATE TYPE bigbase36 ( INPUT = bigbase36_in, OUTPUT = bigbase36_out, LIKE = bigint ); CREATE FUNCTION bigbase36_eq(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8eq'; CREATE FUNCTION bigbase36_ne(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ne'; CREATE FUNCTION bigbase36_lt(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8lt'; CREATE FUNCTION bigbase36_le(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8le'; CREATE FUNCTION bigbase36_gt(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8gt'; CREATE FUNCTION bigbase36_ge(bigbase36, bigbase36) RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ge'; CREATE FUNCTION bigbase36_cmp(bigbase36, bigbase36) RETURNS integer LANGUAGE internal IMMUTABLE AS 'btint8cmp'; CREATE FUNCTION hash_bigbase36(bigbase36) RETURNS integer LANGUAGE internal IMMUTABLE AS 'hashint8'; CREATE OPERATOR = ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_eq, COMMUTATOR = '=', NEGATOR = '<>', RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES ); CREATE OPERATOR <> ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_ne, COMMUTATOR = '<>', NEGATOR = '=', RESTRICT = neqsel, JOIN = neqjoinsel ); CREATE OPERATOR < ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_lt, COMMUTATOR = > , NEGATOR = >= , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR <= ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_le, COMMUTATOR = >= , NEGATOR = > , RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OPERATOR > ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_gt, COMMUTATOR = < , NEGATOR = <= , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR >= ( LEFTARG = bigbase36, RIGHTARG = bigbase36, PROCEDURE = bigbase36_ge, COMMUTATOR = <= , NEGATOR = < , RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OPERATOR CLASS btree_bigbase36_ops DEFAULT FOR TYPE bigbase36 USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 bigbase36_cmp(bigbase36, bigbase36); CREATE OPERATOR CLASS hash_bigbase36_ops DEFAULT FOR TYPE bigbase36 USING hash AS OPERATOR 1 = , FUNCTION 1 hash_bigbase36(bigbase36); CREATE CAST (bigint as bigbase36) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (bigbase36 as bigint) WITHOUT FUNCTION AS ASSIGNMENT;
如你所见,这主要是针对base36
到bigbase36
和int4
到int8
的查找和替换。
如今来添加C语言部分
为了更好地组织c代码,咱们将把base36.c放在src目录下。
mkdir src mv base36.c src/
如今,咱们能够为src
中的bigbase36
输入和输出函数添加另外一个文件。
文件名:src/bigbase64.c
PG_FUNCTION_INFO_V1(bigbase36_in); Datum bigbase36_in(PG_FUNCTION_ARGS) { long result; char *bad; char *str = PG_GETARG_CSTRING(0); result = strtol(str, &bad, 36); if (bad[0] != '\0' || strlen(str)==0) ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid input syntax for bigbase36: \"%s\"", str) ) ); if (result < 0) ereport(ERROR, ( errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("negative values are not allowed"), errdetail("value %ld is negative", result), errhint("make it positive") ) ); PG_RETURN_INT64((int64)result); } PG_FUNCTION_INFO_V1(bigbase36_out); Datum bigbase36_out(PG_FUNCTION_ARGS) { int64 arg = PG_GETARG_INT64(0); if (arg < 0) ereport(ERROR, ( errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("negative values are not allowed"), errdetail("value %d is negative", arg), errhint("make it positive") ) ); char base36[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; /* max 13 char + '\0' */ char buffer[14]; unsigned int offset = sizeof(buffer); buffer[--offset] = '\0'; do { buffer[--offset] = base36[arg % 36]; } while (arg /= 36); PG_RETURN_CSTRING(pstrdup(&buffer[offset])); }
它或多或少与base36的代码相同。在bigbase36_in中,咱们再也不须要溢出安全类型转换为int32,而且能够用PG_RETURN_INT64直接返回结果(result);。对于bigbase36_out,咱们将缓冲区扩展为14个字符,由于结果可能很长。
为了可以将两个文件编译成一个共享库对象,咱们还须要调整Makefile。
文件名:Makefile
# the extensions name EXTENSION = base36 DATA = $(wildcard *--*.sql) # script files to install TESTS = $(wildcard test/sql/*.sql) # use test/sql/*.sql as testfiles # find the sql and expected directories under test # load plpgsql into test db # load base36 extension into test db # dbname REGRESS_OPTS = --inputdir=test \ --load-extension=base36 \ --load-language=plpgsql REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) # object files # final shared library to be build from multiple source files (OBJS) MODULE_big = $(EXTENSION) # postgres build stuff PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS)
在这里(第13行),咱们定义全部src/*。c文件将成为目标文件,应该从这些多个对象构建在一个共享库中(第15行)。
所以,咱们再次将Makefile通常化以备未来使用。
若是咱们如今构建并测试扩展,那么一切都会很好。
可是,咱们还应该为bigbase36类型添加测试。
文件名:sql/bigbase36_io.sql
-- simple input SELECT '120'::bigbase36; SELECT '3c'::bigbase36; -- case insensitivity SELECT '3C'::bigbase36; SELECT 'FoO'::bigbase36; -- invalid characters SELECT 'foo bar'::bigbase36; SELECT 'abc$%2'::bigbase36; -- negative values SELECT '-10'::bigbase36; -- to big values SELECT 'abcdefghijklmn'::bigbase36; -- storage BEGIN; CREATE TABLE base36_test(val bigbase36); INSERT INTO base36_test VALUES ('123'), ('3c'), ('5A'), ('zZz'); SELECT * FROM base36_test; UPDATE base36_test SET val = '567a' where val = '123'; SELECT * FROM base36_test; UPDATE base36_test SET val = '-aa' where val = '3c'; SELECT * FROM base36_test; ROLLBACK;
若是咱们看看results / bigbase36_io.out
,咱们会再次看到一些过于大的值的奇怪行为。
-- to big values SELECT 'abcdefghijklmn'::bigbase36; ERROR: negative values is not allowed LINE 1: SELECT 'abcdefghijklmn'::bigbase36; ^ DETAIL: value -1 is negative HINT: make it positive```
您将注意到,若是结果溢出,strtol()
将返回LONG MAX
。若是您查看一下在postgres源代码中如何将文本转换为数字,您能够看到有许多特定于平台的边和边角状况。为简单起见,咱们假设咱们处于具备64位长结果的64位环境中。在32位机器上,咱们的测试套件会使installcheck失败,告诉咱们的用户扩展不会像预期的那样工做。
文件名:sec/bigbase36.c
#include "postgres.h" #include "fmgr.h" #include "utils/builtins.h" #include <limits.h> PG_FUNCTION_INFO_V1(bigbase36_in); Datum bigbase36_in(PG_FUNCTION_ARGS) { long result; char *bad; char *str = PG_GETARG_CSTRING(0); result = strtol(str, &bad, 36); if (result == LONG_MIN || result == LONG_MAX) ereport(ERROR, ( errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("base36 out of range") ) ); if (bad[0] != '\0' || strlen(str)==0) ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid input syntax for bigbase36: \"%s\"", str) ) ); if (result < 0) ereport(ERROR, ( errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("negative values are not allowed"), errdetail("value %ld is negative", result), errhint("make it positive") ) ); PG_RETURN_INT64((int64)result); } /* bigbase36_out omitted */
在这里,经过包含<limits.h>,咱们能够检查结果是否溢出。这一样适用于base36_in
检查result < INT_MIN || result > INT_MAX
,从而避免`DirectFunctionCall1(int84,result)
。这里惟一须要注意的是,咱们不能将LONG MAX
和LONG MIN
转换为base36
。
如今咱们已经建立了一堆代码复制,让咱们使用一个公共头文件来提升可读性,并在宏中定义错误。
文件名:src/base36.c
#ifndef BASE36_H #define BASE36_H #include "postgres.h" #include "utils/builtins.h" #include "utils/int8.h" #include "libpq/pqformat.h" #include <limits.h> extern const char base36_digits[36]; #define BASE36OUTOFRANGE_ERROR(_str, _typ) \ do { \ ereport(ERROR, \ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), \ errmsg("value \"%s\" is out of range for type %s", \ _str, _typ))); \ } while(0) \ #define BASE36SYNTAX_ERROR(_str, _typ) \ do { \ ereport(ERROR, \ (errcode(ERRCODE_SYNTAX_ERROR), \ errmsg("invalid input syntax for %s: \"%s\"", \ _typ, _str))); \ } while(0) \ #endif // BASE36_H
此外,咱们没有理由不容许负值。
最后咱们的新版本已准备好发布! 咱们来添加一个更新测试。
文件名:test/sql/update.sql
BEGIN; DROP EXTENSION base36; CREATE EXTENSION base36 VERSION '0.0.1'; ALTER EXTENSION base36 UPDATE TO '0.0.2'; SELECT 'abcdefg'::bigbase36;
以后运行
make clean && make && make install && make installcheck
咱们看到
文件名:results/update.out
EGIN; DROP EXTENSION base36; CREATE EXTENSION base36 VERSION '0.0.1'; ALTER EXTENSION base36 UPDATE TO '0.0.2'; ERROR: extension "base36" has no update path from version "0.0.1" to version "0.0.2" SELECT 'abcdefg'::bigbase36; ERROR: current transaction is aborted, commands ignored until end of transaction block
虽然存在0.0.2版本,可是咱们不能运行Update命令。咱们须要一个extension--oldversion--newversion.sql
形式的更新脚本,这个脚本包括从一个版本升级到另外一个版本所需的全部命令。
因此咱们须要将全部base36实现的sql复制到base36--0.0.1--0.0.2.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 bigbase36_in(cstring) RETURNS bigbase36 AS '$libdir/base36' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION bigbase36_out(bigbase36) RETURNS cstring AS '$libdir/base36' LANGUAGE C IMMUTABLE STRICT; CREATE TYPE bigbase36 ( INPUT = bigbase36_in, OUTPUT = bigbase36_out, LIKE = bigint ); ---... rest omitted
对于使用C-Function定义的AS'$ libdir / base36'的每一个SQL函数,都在告诉Postgres使用哪一个共享库。若是重命名共享库,则须要重写全部SQL函数。咱们能够更好的处理这个:
文件名:base36.control
# base36 extension comment = 'base36 datatype' default_version = '0.0.2' relocatable = true module_pathname = '$libdir/base36'
这里咱们定义module_pathname
指向'$ libdir / base36'
,所以咱们能够像这样定义咱们的SQL函数
CREATE FUNCTION base36_in(cstring) RETURNS base36 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT;
在过去五篇文章中,你看到你能够定义本身的数据类型并彻底指定所需的行为。然而,权力越大,责任越大。你不只能用意外的结果将将用户弄晕,还能彻底破坏服务器并丢失数据。幸运的是,你学会了如何调试和编写正确的测试。
在开始实现以前,您应该首先看看Postgres是如何实现的,并尽量地重用功能。所以,您不只避免了重复开发,并且还拥有来自通过良好测试的PostgreSQL代码库的可信代码。完成后,请务必始终考虑边缘状况,将全部内容写入测试以防止破坏,并尝试更高的工做负载和复杂的语句,以免之后在生产环境中出现错误。
因为测试是如此重要,咱们在adjust编写了本身的测试工具pg_spec。 咱们将在下一篇文章中介绍这一点。