CREATE TYPE
CREATE TYPE — 定义一种新的数据类型
大纲
CREATE TYPEnameAS (attribute_namedata_type[, ... ] ) CREATE TYPEnameAS ENUM ( 'label' [, ... ] ) CREATE TYPEname( INPUT =input_function, OUTPUT =output_function[ , RECEIVE =receive_function] [ , SEND =send_function] [ , TYPMOD_IN =type_modifier_input_function] [ , TYPMOD_OUT =type_modifier_output_function] [ , ANALYZE =analyze_function] [ , INTERNALLENGTH = {internallength| VARIABLE } ] [ , PASSEDBYVALUE ] [ , ALIGNMENT =alignment] [ , STORAGE =storage] [ , DEFAULT =default] [ , ELEMENT =element] [ , DELIMITER =delimiter] ) CREATE TYPEname
描述
CREATE TYPE在当前数据库中注册一种新的数据类型。定义该类型的用户将成为其拥有者。
如果给出了模式名,则该类型会在指定模式中创建;否则会在当前模式中创建。类型名必须不同于同一模式中任何现有类型或域的名称。(由于表也具有关联的数据类型,类型名还必须不同于同一模式中任何现有表的名称。)
复合类型
第一种形式的CREATE TYPE创建一个复合类型。复合类型由属性名和数据类型的列表指定。它本质上与表的行类型相同,但如果想要的只是定义一种类型,使用CREATE TYPE可以避免创建一个实际的表。独立的复合类型可用作函数的参数类型或返回类型。
枚举类型
如第 8.7 节中所述,第二种形式的
CREATE TYPE创建枚举(enum)类型。枚举类型接受一个带引号标签的列表,其中每个标签的长度都必须小于
NAMEDATALEN字节(在标准
PostgreSQL构建中为 64 字节)。
基础类型
第四种形式的CREATE TYPE创建一种新的基础类型(标量类型)。这些参数可以按任意顺序出现,不必局限于上面展示的顺序,而且大多数都是可选的。在定义该类型之前,必须先注册两个或更多函数(使用
CREATE FUNCTION)。支持函数
input_function和
output_function是必需的;receive_function、send_function、type_modifier_input_function、type_modifier_output_function、analyze_function则是可选的。通常这些函数必须用 C 或其他低级语言编写。
input_function将类型的外部文本表示转换为该类型的操作符和函数所使用的内部表示。output_function
执行相反的转换。输入函数可以声明为接受一个cstring
参数,或者接受三个参数,类型分别为cstring、oid、integer。第一个参数是以 C 字符串表示的输入文本,第二个参数是该类型自身的 OID(数组类型例外,此时传入的是其元素类型的 OID),第三个参数是在已知情况下目标列的
typmod(未知则传入 -1)。输入函数必须返回该数据类型本身的值。通常输入函数应声明为 STRICT;否则,在读取 NULL
输入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在这种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝
NULL 输入的域输入函数。)输出函数必须声明为接受一个新数据类型参数,并且必须返回cstring类型。对于 NULL 值不会调用输出函数。
可选的receive_function
把类型的外部二进制表示转换为内部表示。如果未提供此函数,该类型就不能参与二进制输入。外部二进制表示应选择为既能低成本转换为内部形式,又具有合理可移植性。(例如,标准整数数据类型把网络字节序用作外部二进制表示,而内部表示则使用机器的本地字节序。)接收函数应进行充分检查以确保值有效。它可以声明为接受一个
internal参数,或者接受三个参数,类型分别为
internal、oid、integer。第一个参数是指向保存已接收字节串的StringInfo缓冲区的指针;其余可选参数与文本输入函数相同。接收函数必须返回该数据类型本身的值。通常,接收函数应声明为 STRICT;否则,在读取 NULL 输入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在这种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝
NULL 输入的域接收函数。)类似地,可选的
send_function把内部表示转换为外部二进制表示。如果未提供此函数,该类型就不能参与二进制输出。发送函数必须声明为接受一个新数据类型参数,并且必须返回
bytea类型。对于 NULL 值不会调用发送函数。
读到这里,你可能会问:既然新类型本身还没创建,输入和输出函数怎么能声明为返回或接受这个新类型呢?答案是,应先把该类型定义为一种
shell 类型,它除了名称和拥有者外没有任何属性,只是一个占位符类型。这可通过执行不带任何附加参数的命令
CREATE TYPE
完成。然后就可以定义引用该 shell 类型的 I/O 函数。最后,再用带完整定义的nameCREATE TYPE替换该 shell 条目,生成一个完整且有效的类型定义,此后新类型就能正常使用。
如果该类型支持修饰符,也就是附加在类型声明上的可选约束,例如
char(5)或numeric(30,2),就需要可选的type_modifier_input_function和
type_modifier_output_function。PostgreSQL允许用户定义类型接受一个或多个简单常量或标识符作为修饰符。不过,这些信息必须能够打包成单个非负整数值,以便存储在系统目录中。声明的修饰符会以
cstring数组的形式传递给type_modifier_input_function。它必须检查这些值是否有效(若无效则抛出错误),若有效则返回一个非负integer值,该值将作为列的“typmod”
存储。如果该类型没有type_modifier_input_function,就会拒绝类型修饰符。type_modifier_output_function
则把内部整数 typmod 值转换回适合用户显示的正确形式。它必须返回一个cstring值,即精确追加到类型名后的字符串;例如,numeric的该函数可能返回
(30,2)。允许省略type_modifier_output_function;在这种情况下,默认显示格式只是把存储的 typmod 整数值放在圆括号中。
可选的analyze_function
为该数据类型的列执行类型专用的统计信息收集。默认情况下,如果该类型有默认的 B-树操作符类,ANALYZE将尝试使用该类型的“equals”和“less-than”操作符收集统计信息。对于非标量类型,这种行为很可能不合适,因此可以通过指定自定义分析函数来覆盖。分析函数必须声明为接受一个
internal参数并返回boolean结果。分析函数的详细 API 见src/include/commands/vacuum.h。
虽然新类型内部表示的细节只有 I/O 函数以及你为该类型编写的其他函数才知道,但仍有若干内部表示属性必须向
PostgreSQL声明。其中最重要的是
internallength。基础类型可以是定长的,此时internallength为正整数;也可以是变长的,此时将internallength设为
VARIABLE。(在内部,这通过把
typlen设为 -1 表示。)所有变长类型的内部表示都必须以一个 4 字节整数开头,用来给出该类型该值的总长度。
可选标志PASSEDBYVALUE表示该数据类型的值按值传递,而不是按引用传递。内部表示大于Datum类型大小(多数机器上为 4 字节,少数为 8 字节)的类型不能按值传递。
alignment参数指定该数据类型所需的存储对齐方式。允许的值分别对应按 1、2、4 或 8 字节边界对齐。注意,变长类型的对齐至少必须为 4,因为它们的第一个组成部分必然是一个int4。
storage参数允许为变长数据类型选择存储策略。(定长类型只允许
plain。)plain表示该类型数据始终内联存储且不压缩。extended表示系统会先尝试压缩较长的数据值,如果仍然过长,就把该值移出主表行。external允许把值移出主表,但系统不会尝试压缩它。main允许压缩,但不鼓励把值移出主表。(采用这种存储策略的数据项在没有其他办法让一行适配时仍可能被移出主表,但与extended和external
数据项相比,它们会被优先保留在主表中。)
如果用户希望该数据类型列的默认值不是空值,可以指定默认值。用
DEFAULT关键字指定默认值。(该默认值可以被附加到具体列上的显式DEFAULT子句覆盖。)
若要表明一种类型是数组类型,可用ELEMENT
关键字指定数组元素的类型。例如,要定义由 4 字节整数(int4)构成的数组,可指定
ELEMENT = int4。更多关于数组类型的细节见下文。
若要指定该类型数组在外部表示中用于分隔各值的分隔符,可把
delimiter设为特定字符。默认分隔符是逗号(,)。注意,这个分隔符关联的是数组元素类型,而不是数组类型本身。
数组类型
每当创建用户定义类型时,PostgreSQL
都会自动创建一个关联的数组类型,其名称由元素类型名前加一个下划线组成;必要时还会截断,以保持其长度小于
NAMEDATALEN字节。(如果这样生成的名称与现有类型名冲突,就会重复这一过程,直到找到不冲突的名称。)这种隐式创建的数组类型是变长的,并使用内置输入/输出函数
array_in和array_out。该数组类型会跟踪其元素类型的拥有者或模式的任何变化,并在元素类型被删除时一并删除。
如果系统会自动创建正确的数组类型,你可能会合理地问,为什么还需要ELEMENT选项。ELEMENT唯一有用的情况是:你正在创建一种定长类型,而它在内部恰好是若干相同元素组成的数组,并且除了为整个类型提供的操作之外,你还希望允许通过下标直接访问这些元素。例如,类型point在内部就表示为两个浮点数,可以用point[0]和
point[1]访问。注意,这种机制只适用于内部形式恰好是一串相同定长字段的定长类型。可用下标访问的变长类型必须采用array_in和array_out使用的通用内部表示。由于历史原因(也就是说这显然不对,但现在改已经太晚了),定长数组类型的下标从零开始,而变长数组则从一开始。
参数
name要创建的类型名称(可选地带有模式限定)。
attribute_name复合类型的一个属性(列)的名称。
data_type将成为复合类型一列的现有数据类型名称。
label表示枚举类型某个值所关联文本标签的字符串字面量。
input_function将数据从类型的外部文本形式转换为内部形式的函数名。
output_function将数据从类型的内部形式转换为外部文本形式的函数名。
receive_function将数据从类型的外部二进制形式转换成内部形式的函数名。
send_function将数据从类型的内部形式转换为外部二进制形式的函数名。
type_modifier_input_function将类型的修饰符数组转换为内部形式的函数名。
type_modifier_output_function将类型修饰符的内部形式转换为外部文本形式的函数名。
analyze_function为该数据类型执行统计分析的函数名。
internallength一个数字常量,用于指定新类型内部表示的字节长度。默认假定它是变长的。
alignment该数据类型的存储对齐需求。如果被指定,它必须是
char、int2、int4或者double。默认是int4。storage该数据类型的存储策略。如果被指定,必须是
plain、external、extended或者main。默认是plain。default该数据类型的默认值。若省略,默认值为 null。
element正在创建的类型是数组;该参数指定数组元素类型。
delimiter在由该类型构成的数组中各值之间使用的分隔符字符。
注解
由于数据类型一旦创建,其使用方式就不再受限制,因此创建基础类型相当于对类型定义中提到的那些函数授予公共执行权限。(因此要求类型的创建者拥有这些函数。)这对适合用于类型定义的那类函数来说通常不是问题。但如果设计一种类型时,需要在把它转换为外部形式或从外部形式转换回来时使用“秘密”信息,就应当三思。
在PostgreSQL 8.3 之前,自动生成的数组类型名称总是恰好等于元素类型名称前加一个下划线字符(_)。(因此,类型名称的长度限制比其他名称少一个字符。)虽然现在通常仍是这样,但在名称达到最大长度或与以下划线开头的用户类型名冲突时,数组类型名称可能与此不同。因此,依赖这一约定编写代码的做法已经弃用。请改用
pg_type.typarray
来定位与给定类型关联的数组类型。
建议避免使用以下划线开头的类型名和表名。虽然服务器会改变生成的数组类型名称以避免与用户给定的名称冲突,但仍然存在混淆风险,特别是对旧客户端软件而言,它们可能会假定以下划线开头的类型名总是表示数组。
在PostgreSQL 8.2 之前,不存在 shell
类型创建语法CREATE TYPE
。创建新基础类型的做法是先创建它的输入函数。在这种做法下,PostgreSQL会首先把新数据类型名视为输入函数的返回类型。此时 shell 类型会被隐式创建,然后就可以在其余 I/O 函数的定义中引用它。这种做法仍然有效,但已弃用,并且可能在未来某个版本中被禁止。另外,为避免由于函数定义中的简单拼写错误而意外使系统目录充满 shell 类型,只有在输入函数用 C 编写时,才会以这种方式创建 shell 类型。
name
在PostgreSQL 7.3 以前的版本中,通常完全不创建 shell 类型,而是将函数中对类型名的前向引用替换为占位伪类型 opaque。在 7.3 以前,cstring参数和结果也必须声明为opaque。为了支持载入旧转储文件,CREATE TYPE会接受使用opaque声明的 I/O 函数,但会发出通知,并修改函数声明以使用正确的类型。
示例
这个示例创建一种复合类型,并在函数定义中使用它:
CREATE TYPE compfoo AS (f1 int, f2 text);
CREATE FUNCTION getfoo() RETURNS SETOF compfoo AS $$
SELECT fooid, fooname FROM foo
$$ LANGUAGE SQL;
这个示例创建一种枚举类型,并在表定义中使用它:
CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed');
CREATE TABLE bug (
id serial,
description text,
status bug_status
);
这个示例创建基础类型box,然后在表定义中使用它:
CREATE TYPE box;
CREATE FUNCTION my_box_in_function(cstring) RETURNS box AS ... ;
CREATE FUNCTION my_box_out_function(box) RETURNS cstring AS ... ;
CREATE TYPE box (
INTERNALLENGTH = 16,
INPUT = my_box_in_function,
OUTPUT = my_box_out_function
);
CREATE TABLE myboxes (
id integer,
description box
);
如果box的内部结构是由四个
float4元素构成的数组,则也可以改为这样写:
CREATE TYPE box (
INTERNALLENGTH = 16,
INPUT = my_box_in_function,
OUTPUT = my_box_out_function,
ELEMENT = float4
);这样就能通过下标访问 box 值的各个分量。除此之外,该类型的行为与前例相同。
这个示例创建一种大对象类型,并在表定义中使用它:
CREATE TYPE bigobj (
INPUT = lo_filein, OUTPUT = lo_fileout,
INTERNALLENGTH = VARIABLE
);
CREATE TABLE big_objs (
id integer,
obj bigobj
);
更多示例(包括配套的输入和输出函数)请见第 34.11 节。
兼容性
这里的CREATE TYPE命令是一个PostgreSQL扩展。SQL标准中也有一个CREATE TYPE语句,但细节上相当不同。