# 38.13.
吐司注意事项如中所述第 38.2 节, PostgreSQL 可以扩展以支持新的数据类型。本节介绍如何定义新的基本类型,即在 SQL 语言级别以下定义的数据类型。
创建一个新的基类型需要实现函数以使用低级语言(通常是 C)对该类型进行操作。本节中的示例可以在
复杂的.sql和
复杂的.c在里面
源/教程源分发目录。见
自述文件
该目录中的文件以获取有关运行示例的说明。用户定义的类型必须始终具有输入和输出功能。这些函数确定类型在字符串中的显示方式(用于用户输入和输出给用户)以及类型在内存中的组织方式。输入函数将一个以空字符结尾的字符串作为其参数,并返回该类型的内部(在内存中)表示。输出函数将类型的内部表示作为参数,并返回一个以空字符结尾的字符串。
如果我们想对类型做更多的事情而不仅仅是存储它,我们必须提供额外的函数来实现我们希望对该类型进行的任何操作。假设我们要定义一个类型
复杂的表示复数。
typedef struct Complex {
double x;
double y;
} Complex;
在内存中表示复数的一种自然方式是以下 C 结构:我们需要将其设为按引用传递的类型,因为它太大而无法放入单个
基准
价值。作为该类型的外部字符串表示,我们选择形式为的字符串
(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 type %s: \"%s\"",
"complex", 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 = psprintf("(%g,%g)", complex->x, complex->y);
PG_RETURN_CSTRING(result);
}
输出函数可以简单地是:您应该小心使输入和输出函数相互反转。如果不这样做,当您需要将数据转储到文件中然后再读回时,您将遇到严重的问题。当涉及浮点数时,这是一个特别常见的问题。
或者,用户定义的类型可以提供二进制输入和输出例程。二进制I/O通常比文本I/O更快,但可移植性较差。与文本I/O一样,外部二进制表示形式的确切定义取决于您。大多数内置数据类型都试图提供独立于机器的二进制表示。对于复杂的
,我们将利用二进制I/O转换器来进行类型转换浮动8
:
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));
}
一旦我们编写了I/O函数并将它们编译成一个共享库,我们就可以定义复杂的
输入SQL。首先,我们将其声明为shell类型:
CREATE TYPE complex;
这是一个占位符,允许我们在定义其I/O函数时引用该类型。现在我们可以定义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会自动为该类型的数组提供支持。数组类型通常与带有下划线字符的基类型具有相同的名称(_
)准备好了。
一旦数据类型存在,我们就可以声明其他函数来提供对该数据类型的有用操作。然后可以在函数顶部定义运算符,如果需要,可以创建运算符类来支持数据类型的索引。下面几节将讨论这些附加层。
如果数据类型的内部表示形式是可变长度,则内部表示形式必须遵循可变长度数据的标准布局:前四个字节必须是字符[4]
从不直接访问的字段(通常命名为沃伦_
).你必须使用SET_VARSIZE()
宏,用于存储该字段中基准面的总大小(包括长度字段本身),以及VARSIZE()
去找回它。(这些宏的存在是因为长度字段可能会根据平台进行编码。)
有关更多详细信息,请参阅创建类型命令
# 38.13.1.敬酒的注意事项
如果数据类型的值大小不同(以内部形式),通常需要使数据类型具有可扩展性(请参见第70.2节)。即使值总是太小,无法在外部压缩或存储,也应该这样做,因为TOAST也可以通过减少标头开销来节省小数据的空间。
为了支持TOAST存储,对数据类型进行操作的C函数必须始终小心地解包它们通过使用PG_DETOAST_基准
(该细节通常通过定义特定类型隐藏。)GETARG_数据类型
宏。)然后,当运行创建类型
命令,将内部长度指定为变量
并选择一些合适的存储选项,而不是平原
.
如果数据对齐不重要(只是针对特定函数,或者因为数据类型指定字节对齐),那么就可以避免数据对齐的一些开销PG_DETOAST_基准
.你可以用PG_DETOAST_DATUM_包装
相反(通常通过定义GETARG_数据类型_PP
宏)和使用宏VARSIZE_ANY_EXHDR
和VARDATA_ANY
访问可能已打包的数据。同样,即使数据类型定义指定了对齐方式,这些宏返回的数据也不会对齐。如果校准很重要,你必须进行常规校准PG_DETOAST_基准
界面
# 笔记
旧代码经常声明沃伦_
作为一个int32
字段而不是字符[4]
。只要结构定义中的其他字段至少包含int32
对齐但在处理可能未对齐的数据时,使用这种结构定义是危险的;编译器可能会将其视为许可证,以假设数据实际上是对齐的,从而导致内核转储到对对齐要求严格的体系结构上。
TOAST支持支持的另一个功能是扩大内存中的数据表示比存储在磁盘上的格式更方便使用。常规或“扁平”varlena存储格式最终只是一个字节块;例如,它不能包含指针,因为它可能会被复制到内存中的其他位置。对于复杂的数据类型,使用平面格式可能非常昂贵,因此PostgreSQL提供了一种方法,可以将平面格式“扩展”为更适合计算的表示形式,然后在数据类型的函数之间在内存中传递该格式。
要使用扩展存储,数据类型必须定义一种扩展格式,该格式遵循中给出的规则src/include/UTIL/expandeddatum.h
,并提供函数,将平面varlena值“展开”为扩展格式,并将展开格式“扁平”回常规varlena表示。然后确保数据类型的所有C函数都可以接受任一表示,可能是在收到时立即将一个转换为另一种表示。这不需要立即修复数据类型的所有现有函数,因为标准PG炣Detaast_u2;基准
宏被定义为将扩展的输入转换为常规的平面格式。因此,使用扁平varlena格式工作的现有函数将继续工作,尽管效率稍低,但扩展输入;在性能良好之前,它们不需要转换,除非性能更好是重要的。
知道如何处理扩展表示的C函数通常分为两类:只能处理扩展格式的函数和可以处理展开或平坦varlena输入的函数。前者更容易编写,但总体上效率可能会更低,因为将平面输入转换为扩展表单供单个函数使用,所需的成本可能比在扩展格式上操作所节省的成本要高。当只需要处理扩展格式时,可以将平面输入转换为扩展表单,隐藏在参数获取宏中,这样函数就不会比使用传统varlena输入的函数复杂。要处理这两种类型的输入,编写一个参数获取函数,该函数将绕行外部、短头和压缩的varlena输入,但不会扩展输入。这样的函数可以定义为返回指向扁平varlena格式和扩展格式的组合的指针。呼叫方可以使用VARATT_u是被展开的\u HEADER()
宏,以确定他们收到的格式。
TOAST基础结构不仅允许将常规varlena值与扩展值区分开来,而且还可以区分“读写”和“只读”指向扩展值的指针。C函数只需要检查扩展值,或者只会以安全和非语义可见的方式更改它,不必关心它们接收的指针类型。如果C函数接收到读写指针,则允许生成修改后的输入值版本的C函数修改适当的扩展输入值,但如果接收到只读指针,则不得修改输入;在这种情况下,它们必须先复制该值,生成一个新的值进行修改。构造了新扩展值的C函数应该始终返回一个读写指针。此外,如果C函数在一部分过程中失败,则应注意将该值保持在一个正常状态。
有关使用扩展值的示例,请参阅标准阵列基础结构,特别是src/backend/utils/adt/array\u扩展.c
.