↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1 / 7.0 / 6.5 / 6.4
历史版本PostgreSQL 7.4 已于 2010 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

33.10. 用户定义的类型 #

如第 33.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。为此,内部表示必须遵循 变长数据的标准布局:前四个字节必须是一个int32, 包含该 datum 的总字节长度(包括其自身)。操作该数据类型的 C 函数必须小心地解开交给它们的任何已 toast 的值(这个细节 通常可以隐藏在GETARG宏中)。然后,在运行 CREATE TYPE命令时,把内部长度指定为 variable并选择合适的存储选项。

更多细节请参阅CREATE TYPE 命令的描述。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。