CREATE TYPE
CREATE TYPE — 定义一种新的数据类型
大纲
CREATE TYPEnameAS ( [attribute_namedata_type[ COLLATEcollation] [, ... ] ] ) CREATE TYPEnameAS ENUM ( [ 'label' [, ... ] ] ) CREATE TYPEnameAS RANGE ( SUBTYPE =subtype[ , SUBTYPE_OPCLASS =subtype_operator_class] [ , COLLATION =collation] [ , CANONICAL =canonical_function] [ , SUBTYPE_DIFF =subtype_diff_function] ) 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] [ , LIKE =like_type] [ , CATEGORY =category] [ , PREFERRED =preferred] [ , DEFAULT =default] [ , ELEMENT =element] [ , DELIMITER =delimiter] [ , COLLATABLE =collatable] ) CREATE TYPEname
描述
CREATE TYPE在当前数据库中注册一种新的数据类型。定义该类型的用户将成为其拥有者。
如果给出了模式名,则该类型会在指定模式中创建;否则会在当前模式中创建。类型名必须不同于同一模式中任何现有类型或域的名称。(由于表也具有关联的数据类型,类型名还必须不同于同一模式中任何现有表的名称。)
如上面的语法概要所示,CREATE TYPE有五种形式。它们分别用于创建复合类型、枚举类型、范围类型、基础类型或shell 类型。下文将依次讨论前四种。shell 类型只是稍后定义某种类型时使用的占位符;它通过执行除类型名外不带任何参数的CREATE TYPE来创建。正如相应小节所述,创建范围类型和基础类型时,需要用 shell 类型作为前向引用。
复合类型
第一种形式的CREATE TYPE创建复合类型。复合类型由属性名和数据类型列表指定。如果某个属性的数据类型支持排序规则,还可以指定该属性的排序规则。复合类型本质上与表的行类型相同,但如果目的只是定义一种类型,使用CREATE TYPE
就不必实际创建表。例如,独立的复合类型可用作函数的参数类型或返回类型。
要创建复合类型,必须对所有属性类型都拥有USAGE
权限。
枚举类型
如第 8.7 节中所述,第二种形式的
CREATE TYPE创建枚举(enum)类型。枚举类型接受一个带引号标签的列表,其中每个标签的长度都必须小于
NAMEDATALEN字节(在标准
PostgreSQL构建中为 64 字节)。
范围类型
如第 8.16 节中所述,第三种形式的
CREATE TYPE创建范围类型。
范围类型的subtype可以是任何带有关联 B-树操作符类的类型(该操作符类用于确定范围类型值的顺序)。通常使用子类型默认的 B-树操作符类来确定顺序;若要使用非默认操作符类,可用subtype_opclass指定其名称。如果子类型支持排序规则,而你希望在范围排序中使用非默认排序规则,可用
collation选项指定所需排序规则。
可选的canonical函数必须接受一个正在定义的范围类型值作为参数,并返回同一类型的值。在适用时,它用于将范围值转换为规范形式。更多信息见第 8.16.8 节。创建
canonical函数有些棘手,因为它必须在声明范围类型之前定义。为此,必须先创建一种 shell
类型,它除了名称和拥有者外没有任何属性,只是一个占位符类型。这可通过执行不带任何附加参数的命令CREATE TYPE
完成。然后就可以把该
shell 类型用作参数类型和结果类型来声明该函数,最后再用同一名称声明范围类型。这样会自动用有效的范围类型替换 shell 类型条目。
name
可选的subtype_diff
函数必须接受两个subtype类型的值作为参数,并返回一个表示这两个给定值之差的double precision值。虽然这是可选的,但提供该函数可显著提高该范围类型列上 GiST 索引的效率。详见第 8.16.8 节。
基础类型
第四种形式的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
数据项相比,它们会被优先保留在主表中。)
like_type参数提供了指定数据类型基本表示属性的另一种方法:从某个现有类型复制这些属性。internallength、passedbyvalue、alignment和
storage的值都从指定类型复制而来。(虽然可以通过同时给出LIKE子句和这些选项来覆盖其中某些值,但通常不建议这样做。)当新类型的底层实现以某种方式“借用”现有类型时,以这种方式指定表示属性尤其有用。
category和
preferred参数可用于在存在歧义时帮助控制应用哪一种隐式类型转换。每种数据类型都属于一个由单个 ASCII 字符命名的类别,并且在其类别内要么是“首选”的,要么不是。当这一规则有助于解析重载函数或操作符时,解析器会优先转换为首选类型(但只会从同一类别中的其他类型转换)。更多细节见第 10 章。对于与任何其他类型之间都没有隐式类型转换的类型,保持这些设置的默认值就足够了。不过,对于一组彼此存在隐式类型转换的相关类型,把它们都标记为属于同一类别,并选择一两个“最通用”的类型作为该类别的首选类型,通常会有帮助。category参数在将用户定义类型加入现有内置类别(例如数值类型或字符串类型)时尤其有用。不过,也可以创建全新的纯用户定义类型类别。为这种类别命名时,可选择任一非大写字母的 ASCII 字符。
如果用户希望该数据类型列的默认值不是空值,可以指定默认值。用
DEFAULT关键字指定默认值。(该默认值可以被附加到具体列上的显式DEFAULT子句覆盖。)
若要表明一种类型是数组类型,可用ELEMENT
关键字指定数组元素的类型。例如,要定义由 4 字节整数(int4)构成的数组,可指定
ELEMENT = int4。更多关于数组类型的细节见下文。
若要指定该类型数组在外部表示中用于分隔各值的分隔符,可把
delimiter设为特定字符。默认分隔符是逗号(,)。注意,这个分隔符关联的是数组元素类型,而不是数组类型本身。
如果可选的布尔参数
collatable为真,则该类型的列定义和表达式可以通过COLLATE子句携带排序规则信息。是否实际使用这些排序规则信息取决于操作该类型的函数实现;仅仅把类型标记为支持排序规则并不会自动实现这一点。
数组类型
每当创建用户定义类型时,PostgreSQL
都会自动创建一个关联的数组类型,其名称由元素类型名前加一个下划线组成;必要时还会截断,以保持其长度小于
NAMEDATALEN字节。(如果这样生成的名称与现有类型名冲突,就会重复这一过程,直到找到不冲突的名称。)这种隐式创建的数组类型是变长的,并使用内置输入/输出函数
array_in和array_out。该数组类型会跟踪其元素类型的拥有者或模式的任何变化,并在元素类型被删除时一并删除。
如果系统会自动创建正确的数组类型,你可能会合理地问,为什么还需要ELEMENT选项。ELEMENT唯一有用的情况是:你正在创建一种定长类型,而它在内部恰好是若干相同元素组成的数组,并且除了为整个类型提供的操作之外,你还希望允许通过下标直接访问这些元素。例如,类型point在内部就表示为两个浮点数,可以用point[0]和
point[1]访问。注意,这种机制只适用于内部形式恰好是一串相同定长字段的定长类型。可用下标访问的变长类型必须采用array_in和array_out使用的通用内部表示。由于历史原因(也就是说这显然不对,但现在改已经太晚了),定长数组类型的下标从零开始,而变长数组则从一开始。
参数
name要创建的类型名称(可选地带有模式限定)。
attribute_name复合类型的一个属性(列)的名称。
data_type将成为复合类型一列的现有数据类型名称。
collation要与复合类型的某一列或范围类型关联的现有排序规则名称。
label表示枚举类型某个值所关联文本标签的字符串字面量。
subtype范围类型所表示范围的元素类型名称。
subtype_operator_class子类型的 B-树操作符类名称。
canonical_function范围类型规范化函数的名称。
subtype_diff_function子类型差分函数的名称。
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。like_type与新类型具有相同表示形式的现有数据类型名称。除非在本
CREATE TYPE命令的其他位置显式覆盖,否则internallength、passedbyvalue、alignment和storage的值都会从该类型复制。category该类型的类别码(单个 ASCII 字符)。默认值是表示“用户定义类型”的
'U'。其他标准类别码见表 47.52。你也可以选择其他 ASCII 字符来创建自定义类别。preferred若该类型是其类型类别中的首选类型,则为真,否则为假。默认值为假。在现有类型类别中创建新的首选类型时要格外小心,因为这可能导致出人意料的行为变化。
default该数据类型的默认值。若省略,默认值为 null。
element正在创建的类型是数组;该参数指定数组元素类型。
delimiter在由该类型构成的数组中各值之间使用的分隔符字符。
collatable如果该类型的操作可以使用排序规则信息,则为真。默认为假。
注解
由于数据类型一旦创建,其使用方式就不再受限制,因此创建基础类型或范围类型,相当于对类型定义中提到的那些函数授予公共执行权限。这对适合用于类型定义的那类函数来说通常不是问题。但如果设计一种类型时,需要在把它转换为外部形式或从外部形式转换回来时使用“秘密”信息,就应当三思。
在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
);
这个示例创建一种范围类型:
CREATE TYPE float8_range AS RANGE (subtype = float8, subtype_diff = float8mi);
这个示例创建基础类型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
);
更多示例(包括配套的输入和输出函数)请见第 35.11 节。
兼容性
第一种CREATE TYPE形式,即创建复合类型的形式,符合SQL标准。其他形式都是
PostgreSQL扩展。SQL标准中的CREATE TYPE
语句还定义了PostgreSQL尚未实现的其他形式。
支持创建零属性的复合类型,是
PostgreSQL对标准的一种特有背离(类似于CREATE TABLE中的同类情况)。