pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
如第 32.2 节中所述, PostgreSQL可以扩展以支持新的数据类型。本节描述 如何定义新的基础类型,也就是在SQL语言层之下定义的 数据类型。创建新的基础类型需要用底层语言(通常是 C)实现操作该类型的 函数。
本节中的示例位于源码分发包的src/tutorial目录中的 complex.sql和complex.c。关于 如何运行这些示例,请参见该目录中的README文件。
用户定义类型必须始终有输入和输出函数。 这些函数决定该类型以字符串形式出现(供用户输入和向用户输出) 的方式,以及该类型在内存中如何组织。输入函数接受一个以空字符 结尾的字符串作为参数,并返回该类型的内部(内存中)表示。输出 函数接受该类型的内部表示作为参数,并返回一个以空字符结尾的 字符串。如果我们希望该类型除了存储之外还能做别的事情,就必须 提供额外的函数来实现我们希望该类型支持的各种操作。
假设我们要定义一种表示复数的类型complex。在内存中表示复 数的一种自然方式是下面这个 C 结构体:
typedef struct Complex {
double x;
double y;
} Complex;
我们需要把它做成按引用传递的类型,因为它太大了,无法放进单个 Datum值中。
作为该类型的外部字符串表示,我们选择形如(x,y)的 字符串。
输入和输出函数通常不难编写,尤其是输出函数。但在定义该类型的 外部字符串表示时,请记住,你最终必须为该表示编写一个完整而 健壮的解析器作为输入函数。例如:
PG_FUNCTION_INFO_V1(complex_in);
Datum
complex_in(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
double x,
y;
Complex *result;
if (sscanf(str, " ( %lf , %lf )", &x, &y) != 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for complex: \"%s\"",
str)));
result = (Complex *) palloc(sizeof(Complex));
result->x = x;
result->y = y;
PG_RETURN_POINTER(result);
}
输出函数可以简单地写成:
PG_FUNCTION_INFO_V1(complex_out);
Datum
complex_out(PG_FUNCTION_ARGS)
{
Complex *complex = (Complex *) PG_GETARG_POINTER(0);
char *result;
result = (char *) palloc(100);
snprintf(result, 100, "(%g,%g)", complex->x, complex->y);
PG_RETURN_CSTRING(result);
}
应当注意让输入函数和输出函数互为逆运算。如果不是这样,当你需要把数据 转储到文件中再读回时,就会遇到严重问题。这在涉及浮点数时尤为常见。
可选地,用户定义类型还可以提供二进制输入和输出例程。二进制 I/O 通常 比文本 I/O 更快,但可移植性较差。与文本 I/O 一样,外部二进制表示的精 确定义完全由你决定。大多数内置数据类型都尽量提供与机器无关的二进制表 示。对于complex,我们将借助类型float8的二 进制 I/O 转换器:
PG_FUNCTION_INFO_V1(complex_recv);
Datum
complex_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
Complex *result;
result = (Complex *) palloc(sizeof(Complex));
result->x = pq_getmsgfloat8(buf);
result->y = pq_getmsgfloat8(buf);
PG_RETURN_POINTER(result);
}
PG_FUNCTION_INFO_V1(complex_send);
Datum
complex_send(PG_FUNCTION_ARGS)
{
Complex *complex = (Complex *) PG_GETARG_POINTER(0);
StringInfoData buf;
pq_begintypsend(&buf);
pq_sendfloat8(&buf, complex->x);
pq_sendfloat8(&buf, complex->y);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
要定义complex类型,我们需要在创建该类型之前先创建 用户定义的 I/O 函数:
CREATE FUNCTION complex_in(cstring)
RETURNS complex
AS 'filename'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION complex_out(complex)
RETURNS cstring
AS 'filename'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION complex_recv(internal)
RETURNS complex
AS 'filename'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION complex_send(complex)
RETURNS bytea
AS 'filename'
LANGUAGE C IMMUTABLE STRICT;
注意,输入和输出函数的声明必须引用尚未定义的类型。这是允许的,但 会产生一些可以忽略的警告消息。输入函数必须放在前面。
最后,我们可以声明该数据类型:
CREATE TYPE complex ( internallength = 16, input = complex_in, output = complex_out, receive = complex_recv, send = complex_send, alignment = double );
当你定义一种新的基本类型时,PostgreSQL 会自动提供对该类型数组的支持。 由于历史原因,该数组类型的名称与基本类型相同, 只是在前面加一个下划线字符(_)。
一旦该数据类型存在,我们就可以声明额外的函数,为该数据类型提供有用的 操作。随后可以在这些函数之上定义操作符;如果需要,还可以创建操作符类 以支持该数据类型的索引。这些附加层会在后续各节中讨论。
如果你的数据类型的值(在内部形式上)可能超过几百字节,就应该让该 数据类型支持 TOAST(见 第 50.2 节)。为此,内部表示必须遵循可变长度数据的标准 布局:前四个字节必须是一个int32,其中包含该 datum 的总 字节长度(包括其自身)。操作该数据类型的 C 函数必须注意使用 PG_DETOAST_DATUM对传给它们的任何已 TOAST 的值 先做解包处理。(这一细节通常通过定义特定于该类型的 GETARG宏来隐藏。)然后,在执行 CREATE TYPE命令时,把内部长度指定为 variable并选择合适的存储选项。
更多细节见CREATE TYPE命令的说明。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。