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] [ , MULTIRANGE_TYPE_NAME =multirange_type_name] ) 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] [ , SUBSCRIPT =subscript_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 权限。
如Section 8.7中所述,第二种形式的 CREATE TYPE创建枚举(enum)类型。枚举类 型接受一个 带引号标签的列表,其中每个标签的长度都必须小于 NAMEDATALEN字节(在标准 PostgreSQL构建中为 64 字节)。(也 可以创建零标签的枚举类型,但在使用ALTER TYPE 至少添加一个标签之前,这种类型不能用来保存值。)
如Section 8.17中所述,第三种形式的 CREATE TYPE创建范围类型。
范围类型的subtype可 以是任何带有关联 B-树操作符类的类型(该操作符类用于确定范围类型值 的顺序)。通常使用子类型默认的 B-树操作符类来确定顺序;若要使用 非默认操作符类,可用subtype_opclass指定其名称。如果 子类型支持排序规则,而你希望在范围排序中使用非默认排序规则,可用 collation选项指定所 需排序规则。
可选的canonical函数 必须接受一个正在定义的范围类型值作为参数,并返回同一类型的值。在 适用时,它用于将范围值转换为规范形式。更多信息见Section 8.17.8。创建 canonical函数有些棘 手,因为它必须在声明范围类型之前定义。为此,必须先创建一种 shell 类型,它除了名称和拥有者外没有任何属性,只是一个占位符类型。这可 通过执行不带任何附加参数的命令CREATE TYPE 完成。然后就可以把该 shell 类型用作参数类型和结果类型来声明该函数,最后再用同一名称声 明范围类型。这样会自动用有效的范围类型替换 shell 类型条目。name
可选的subtype_diff 函数必须接受两个subtype类型的值作为参数,并返回 一个表示这两个给定值之差的double precision值。虽 然这是可选的,但提供该函数可显著提高该范围类型列上 GiST 索引的效 率。详见Section 8.17.8。
可选的multirange_type_name参数指定对应 多范围类型的名称。若未指定,将按如下规则自动选择:如果范围类型 名称包含子串range,则把类型名中的 range替换为multirange, 作为多范围类型名;否则就在范围类型名后附加 _multirange后缀。
第四种形式的CREATE TYPE创建一种新的基础类 型(标量类型)。要创建新的基础类型,你必须是超级用户。(这样限制 是因为错误的类型定义可能使服务器陷入混乱,甚至导致其崩溃。)
这些参数可以按任意顺序出现,不必局限于上面展示的顺序,而且大多数 都是可选的。在定义该类型之前,必须先注册两个或更多函数(使用 CREATE FUNCTION)。支持函数 input_function和 output_function是必 需的;receive_function、 send_function、 type_modifier_input_function、 type_modifier_output_function、 analyze_function以及 subscript_function则 是可选的。通常这些函数必须用 C 或其他低级语言编写。
input_function将 类型的外部文本表示转换为该类型的操作符和函数所使用的内部表 示。output_function 执行相反的转换。输入函数可以声明为接受一个cstring 参数,或者接受三个参数,类型分别为cstring、 oid、integer。第一个参数是以 C 字符 串表示的输入文本,第二个参数是该类型自身的 OID(数组类型例外,此 时传入的是其元素类型的 OID),第三个参数是在已知情况下目标列的 typmod(未知则传入 -1)。输入函数必须返回该 数据类型本身的值。通常输入函数应声明为 STRICT;否则,在读取 NULL 输入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在 这种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝 NULL 输入的域输入函数。)输出函数必须声明为接受一个新数据类型参 数,并且必须返回cstring类型。对于 NULL 值不会调用 输出函数。
可选的receive_function 把类型的外部二进制表示转换为内部表示。如果未提供此函数,该类型就 不能参与二进制输入。外部二进制表示应选择为既能低成本转换为内部形 式,又具有合理可移植性。(例如,标准整数数据类型把网络字节序用作 外部二进制表示,而内部表示则使用机器的本地字节序。) receive_function 应进行充分检查以确保值有效。它可以声明为接受一个 internal参数,或者接受三个参数,类型分别为 internal、oid、integer。 第一个参数是指向保存已接收字节串的StringInfo缓冲区 的指针;其余可选参数与文本输入函数相同。接收函数必须返回该数据类 型本身的值。通常,接收函数应声明为 STRICT;否则,在读取 NULL 输 入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在这 种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝 NULL 输入的域接收函数。)类似地,可选的 send_function把内 部表示转换为外部二进制表示。如果未提供此函数,该类型就不能参与二 进制输出。发送函数必须声明为接受一个新数据类型参数,并且必须返回 bytea类型。对于 NULL 值不会调用发送函数。
读到这里,你可能会问:既然新类型本身还没创建,输入和输出函数怎么 能声明为返回或接受这个新类型呢?答案是,应先把该类型定义为一种 shell 类型,它除了名称和拥有者外没有任何 属性,只是一个占位符类型。这可通过执行不带任何附加参数的命令 CREATE TYPE 完成。然后就可以定义引用该 shell 类型的 C 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。
可选的subscript_function允许在 SQL 命 令中对该数据类型进行下标访问。指定此函数并不会使该类型被视为 “真正的”数组类型;例如,它不会成为 ARRAY[]构造结果类型的候选者。但如果对这种类 型的值使用下标是提取其中数据的一种自然记法,就可以编写 subscript_function 来定义其含义。下标函数必须声明为接受一个internal 参数,并返回一个internal结果,也就是指向实现下标 操作的方法(函数)结构体的指针。下标函数的详细 API 见 src/include/nodes/subscripting.h。阅读 src/backend/utils/adt/arraysubs.c中的数组 实现,或更简单的contrib/hstore/hstore_subs.c 代码,也可能有所帮助。更多信息见下文Array Types。
虽然新类型内部表示的细节只有 I/O 函数以及你为该类型编写的其他函 数才知道,但仍有若干内部表示属性必须向 PostgreSQL声明。其中最重要的是 internallength。 基础数据类型可以是定长的,此时internallength为正整数;也可 以是变长的,此时将internallength设为 VARIABLE。(在内部,这通过把 typlen设为 -1 表示。)所有变长类型的内部表 示都必须以一个 4 字节整数开头,用来给出该类型该值的总长度。(注 意,如Section 68.2中所述,长度字段通常是经 过编码的;直接访问它并不明智。)
可选标志PASSEDBYVALUE表示该数据类型的值按值 传递,而不是按引用传递。按值传递的类型必须是定长的,且其内部表示 不能大于Datum类型的大小(某些机器上为 4 字节,另 一些为 8 字节)。
alignment参数指定该 数据类型所需的存储对齐方式。允许的值分别对应按 1、2、4 或 8 字节 边界对齐。注意,变长类型的对齐至少必须为 4,因为它们的第一个组成 部分必然是一个int4。
storage参数允许为 变长数据类型选择存储策略。(定长类型只允许 plain。)plain表示该类型 数据始终内联存储且不压缩。extended表示系统 会先尝试压缩较长的数据值,如果仍然过长,就把该值移出主表行。 external允许把值移出主表,但系统不会尝试压缩 它。main允许压缩,但不鼓励把值移出主表。(采 用这种存储策略的数据项在没有其他办法让一行适配时仍可能被移出主 表,但与extended和external 数据项相比,它们会被优先保留在主表中。)
如Section 68.2和Section 36.13.1所述,除plain之外所 有storage值都意味 着该数据类型的函数能够处理经过TOAST的 值。具体指定哪一种其他值,只是决定可 TOAST 数据类型列的默认 TOAST 存储策略;用户仍可使用ALTER TABLE SET STORAGE 为单个列选择其他策略。
like_type参数提供 了指定数据类型基本表示属性的另一种方法:从某个现有类型复制这些属 性。internallength、 passedbyvalue、 alignment和 storage的值都从指 定类型复制而来。(虽然可以通过同时给出LIKE子 句和这些选项来覆盖其中某些值,但通常不建议这样做。)当新类型的底 层实现以某种方式“借用”现有类型时,以这种方式指定表 示属性尤其有用。
category和 preferred参数可用 于在存在歧义时帮助控制应用哪一种隐式类型转换。每种数据类型都属于 一个由单个 ASCII 字符命名的类别,并且在其类别内要么是 “首选”的,要么不是。当这一规则有助于解析重载函数或 操作符时,解析器会优先转换为首选类型(但只会从同一类别中的其他类 型转换)。更多细节见Chapter 10。对于与任何其他 类型之间都没有隐式类型转换的类型,保持这些设置的默认值就足够了。 不过,对于一组彼此存在隐式类型转换的相关类型,把它们都标记为属于 同一类别,并选择一两个“最通用”的类型作为该类别的 首选类型,通常会有帮助。category参数在将用户定义类型加 入现有内置类别(例如数值类型或字符串类型)时尤其有用。不过,也可 以创建全新的纯用户定义类型类别。为这种类别命名时,可选择任一非大 写字母的 ASCII 字符。
如果用户希望该数据类型列的默认值不是空值,可以指定默认值。用 DEFAULT关键字指定默认值。(该默认值可以被附 加到具体列上的显式DEFAULT子句覆盖。)
若要表明一种类型是定长数组类型,可用ELEMENT 关键字指定数组元素的类型。例如,要定义由 4 字节整数 (int4)构成的数组,可指定 ELEMENT = int4。更多细节见下文Array Types。
若要指定该类型数组在外部表示中用于分隔各值的分隔符,可把 delimiter设为特定 字符。默认分隔符是逗号(,)。注意,这个分隔 符关联的是数组元素类型,而不是数组类型本身。
如果可选的布尔参数 collatable为真,则 该类型的列定义和表达式可以通过COLLATE子句携 带排序规则信息。是否实际使用这些排序规则信息取决于操作该类型的函 数实现;仅仅把类型标记为 collatable 并不会自动实现这一点。
每当创建用户定义类型时,PostgreSQL 都会自动创建一个关联的数组类型,其名称由元素类型名前加一个下划线 组成;必要时还会截断,以保持其长度小于 NAMEDATALEN字节。(如果这样生成的名称与现有 类型名冲突,就会重复这一过程,直到找到不冲突的名称。)这种隐式 创建的数组类型是变长的,并使用内置输入/输出函数 array_in和array_out。 此外,系统在处理基于该用户定义类型的ARRAY[] 等构造时,使用的也是这种类型。该数组类型会跟踪其元素类型的拥有者 或模式的任何变化,并在元素类型被删除时一并删除。
如果系统会自动创建正确的数组类型,你可能会合理地问,为什么还需 要ELEMENT选项。ELEMENT最主 要的用途是:你正在创建一种定长类型,而它在内部恰好是若干相同元素 组成的数组,并且除了为整个类型提供的操作之外,你还希望允许通过下 标直接访问这些元素。例如,类型point在内部就表示为 两个浮点数,可以用point[0]和 point[1]访问。注意,这种机制只适用于内部形 式恰好是一串相同定长字段的定长类型。由于历史原因(也就是说这显然 不对,但现在改已经太晚了),定长数组类型的下标从零开始,而变长数 组则从一开始。
指定SUBSCRIPT选项允许对某种数据类型使用下标, 即使系统在其他方面并不把它视为数组类型。上面刚描述的定长数组行 为,实际上是由SUBSCRIPT处理函数 raw_array_subscript_handler实现的;如果 你为定长类型指定了ELEMENT而没有同时写 SUBSCRIPT,系统就会自动使用该函数。
指定自定义SUBSCRIPT函数时,通常不必再指定 ELEMENT,除非SUBSCRIPT处 理函数需要查阅typelem来确定应返回 什么。请注意,指定ELEMENT会让系统认为新类型包 含元素类型,或者在物理上以某种方式依赖元素类型;因此,例如,如果 存在该依赖类型的列,就不允许更改元素类型的属性。
name #要创建的类型名称(可选地带有模式限定)。
attribute_name #复合类型的一个属性(列)的名称。
data_type #将成为复合类型一列的现有数据类型名称。
collation #要与复合类型的某一列或范围类型关联的现有排序规则名称。
label #表示枚举类型某个值所关联文本标签的字符串字面量。
subtype #范围类型所表示范围的元素类型名称。
subtype_operator_class #子类型的 B-树操作符类名称。
canonical_function #范围类型规范化函数的名称。
subtype_diff_function #子类型差分函数的名称。
multirange_type_name #对应多范围类型的名称。
input_function #将数据从类型的外部文本形式转换为内部形式的函数名。
output_function #将数据从类型的内部形式转换为外部文本形式的函数名。
receive_function #将数据从类型的外部二进制形式转换成内部形式的函数名。
send_function #将数据从类型的内部形式转换为外部二进制形式的函数名。
type_modifier_input_function #将类型的修饰符数组转换为内部形式的函数名。
type_modifier_output_function #将类型修饰符的内部形式转换为外部文本形式的函数名。
analyze_function #为该数据类型执行统计分析的函数名。
subscript_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'。其他标准 类别码见Table 50.63。你也可以 选择其他 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 16 及更高版本中,基础类 型的输入函数最好使用新的 errsave()/ereturn()机 制返回“软”错误,而不是像早期版本那样抛出 ereport()异常。更多信息见 src/backend/utils/fmgr/README。
这个示例创建一种复合类型,并在函数定义中使用它:
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
);
更多示例(包括配套的输入和输出函数)请见Section 36.13。
第一种CREATE TYPE形式,即创建复合类型的形式, 符合SQL标准。其他形式都是 PostgreSQL扩展。 SQL标准中的CREATE TYPE 语句还定义了PostgreSQL尚未实现的其 他形式。
支持创建零属性的复合类型,是 PostgreSQL对标准的一种特有背离(类 似于CREATE TABLE中的同类情况)。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。