在关于编写Postgres扩展的系列文章的最后四篇文章中,我们了解了基本的类型和操作符,介绍了调试器并完成了测试套件。

现在让我们添加另一种类型,看看如何在代码库增长时组织代码库。

你可以在github分支上找到最后一篇帖子的代码库part_iv今天的分支可以在分支part_v上找到

版本控制

我们可能对我们的扩展感到满意并在生产中使用它一段时间没有任何问题。现在我们的业务成功了,int的范围可能已经不够了。 这意味着我们需要另一个基于bigint的类型bigbase36,最多可以包含13个字符。

这里的问题是我们不能简单地删除扩展并重新安装新版本。

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文件:

文件名: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;

如你所见,这主要是针对base36bigbase36int4int8的查找和替换。

现在来添加C语言部分

组织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 MAXLONG 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

MODULE_PATHNAME

对于使用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。 我们将在下一篇文章中介绍这一点。

编写Postgres扩展之五:代码组织和版本控制的更多相关文章

  1. 编写Postgres扩展之四:测试

    原文:http://big-elephants.com/2015-11/writing-postgres-extensions-part-iv/ 编译:http://big-elephants.com ...

  2. 编写Postgres扩展之一:基础

    原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-i/ 编译:Tacey Wong Postgres提供了广泛的 ...

  3. 编写Postgres扩展之三:调试

    原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-iii/ 编译:Tacey Wong 在上一篇关于编写Post ...

  4. 编写Postgres扩展之二:类型和运算符

    原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-ii/ 编译:Tacey Wong 在上一篇关于编写Postg ...

  5. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  6. 使用golang 编写postgresql 扩展

      postgresql 的扩展可以帮助我们做好多强大的事情,支持的开发语言有lua.perl.java.js.c 社区有人开发了一个可以基于golang开发pg 扩展的项目,使用起来很方便,同时为我 ...

  7. 如何提升我的HTML&CSS技术,编写有结构的代码

    前言 之前写了四篇HTML和CSS的知识点,也相当于是一个知识点汇总.有需要的可以收藏,平时开发过程中应该会遇到这些点,到时候再查看这些博客可能更容易理解.从这篇开始更多的介绍开发过程经常让人头痛的前 ...

  8. 编写高质量JS代码的68个有效方法(八)

    [20141227]编写高质量JS代码的68个有效方法(八) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  9. JavaScript手札:《编写高质量JS代码的68个有效方法》(一)(1~5)

    编写高质量JS代码的68个有效方法(一) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-botto ...

随机推荐

  1. ubuntu18 更换屏幕分辨率

    ubuntu18.04怎么修改屏幕分辨率 最近在自己的电脑中安装了ubuntu18.04系统,默认分辨率不对所以只好自己修改分辨率,但是在桌面右键并没找到设置分辨率的选项,那么我们应该在哪里设置分辨率 ...

  2. Docs-.NET-C#-指南-语言参考-关键字-内置类型-值类型:整型数值类型

    ylbtech-Docs-.NET-C#-指南-语言参考-关键字-内置类型-值类型:整型数值类型 1.返回顶部 1. 整型数值类型(C# 参考) 2019/10/22 “整型数值类型”是“简单类型”的 ...

  3. npm WARN deprecated fsevents windows

    更新下 使用yarn貌似会帮助跳过这个问题: info fsevents@2.1.2: The platform "win32" is incompatible with this ...

  4. Angular 项目打包之后,部署到springboot项目中,刷新访问404解决方法

    解决方法1: app.module.ts文件添加两行代码: import { LocationStrategy, HashLocationStrategy } from '@angular/commo ...

  5. setInterval调用ajax回调函数不执行的问题

    setInterval调用ajax回调函数不执行 1.首先检查你的setInterval()函数写法是否正确 参考写法 // 检查是否支付成功 var isPayRequest=false; var ...

  6. Opencv图片明暗处理

    Opencv图片明暗处理 #include <iostream> #include <opencv2/opencv.hpp> using namespace std; usin ...

  7. hadoop记录-flink测试

    1.启动集群 bin/start-cluster.sh 2.jps查看进程 3.打开网页端(192.168.66.128:8081) 4.造数据:nc -lk 9000 5.执行./bin/flink ...

  8. Spring Cloud Eureka 服务发现 4.2

      在微服务架构中,服务发现可以说是最为核心和基础的模块,该模块主要用于实现各个微服务实例的自动化注册与发现.在Spring Cloud的子项目中,Spring Cloud Netflix提供了Eur ...

  9. Can't accept UDP connections java.net.BindException: Address already in use_解决方案

    一.问题描述 在Linux服务器(CentOS7系统)中配置并启动JMeter远程监控服务器资源所需的ServerAgent目录下的 startAgent.sh 文件时,系统出现异常提示,如下: [r ...

  10. [计算机视觉][ARM-Linux开发]OpenCV 3.1下载 ippicv_linux_20151201失败

    安装OpenCV 3.1的过程中要下载ippicv_linux_20151201,由于网络的原因,这个文件经常会下载失败. 解决的办法是手动下载: 先下载 OpenCV 3.1 Download MD ...