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 有五种形式。它们分别创建复合类型、枚举类型、范围类型、基本类型或者外壳类型。前四种将在下面依次讨论。外壳类型只是一个占位符,用于以后要定义的类型;它通过发出除类型名称外不带任何参数的 CREATE TYPE 命令来创建。在创建范围类型和基本类型时需要用外壳类型作为前向引用,这将在那些章节中讨论。
CREATE TYPE 的第一种形式创建一个复合类型。复合类型由属性名称和数据类型的列表指定。如果属性的数据类型是可排序的,也可以指定该属性的排序规则。复合类型本质上与表的行类型相同,但是当只想要定义一种类型时,使用 CREATE TYPE 可以避免创建实际的表。独立的复合类型很有用,例如可以作为函数的参数类型或返回类型。
要创建复合类型,你必须对所有属性类型拥有 USAGE 特权。
CREATE TYPE 的第二种形式创建一个枚举(enum)类型,如 Section 8.7 中所述。枚举类型需要一个带引号的标签列表,每个标签的长度必须小于 NAMEDATALEN 字节(在标准的 PostgreSQL 构建中为 64 字节)。(可以创建一个没有任何标签的枚举类型,但在使用 ALTER TYPE 添加至少一个标签之前,这样的类型不能用来保存值。)
CREATE TYPE 的第三种形式创建一个新的范围类型,如 Section 8.17 中所述。
范围类型的 subtype 可以是任何具有关联 b-tree 操作符类的类型(用于确定范围类型中值的排序顺序)。通常使用子类型的默认 b-tree 操作符类来确定排序;要使用非默认的操作符类,请用 subtype_opclass 指定其名称。如果子类型是可排序的,并且你想在范围的排序中使用非默认的排序规则,请用 collation 选项指定所需的排序规则。
可选的 canonical 函数必须接受正在定义的范围类型的一个参数,并返回相同类型的值。在适用的情况下,它用于把范围值转换为规范形式。更多信息请参见 Section 8.17.8。创建 canonical 函数有点棘手,因为必须先定义它才能声明范围类型。为此,你必须首先创建一个外壳类型,这是一种除了名称和拥有者之外没有任何属性的占位符类型。这可以通过发出命令 CREATE TYPE (不带任何附加参数)来完成。然后可以使用该外壳类型作为参数和结果来声明函数,最后使用相同的名称声明范围类型。这会自动用一个有效的范围类型替换外壳类型条目。 name
可选的 subtype_diff 函数必须接受两个 subtype 类型的值作为参数,并返回一个表示两个给定值之差的 double precision 值。虽然这是可选的,但提供它可以显著提高范围类型列上 GiST 索引的效率。更多信息请参见 Section 8.17.8。
可选的 multirange_type_name 参数指定相应多范围类型的名称。如果没有指定,该名称将按如下方式自动选择。如果范围类型的名称包含子串 range,那么多范围类型的名称是通过把范围类型名称中的 range 子串替换为 multirange 而形成的。否则,多范围类型的名称是通过在范围类型名称后面附加 _multirange 后缀而形成的。
要创建范围类型,你必须对子类型拥有 USAGE 特权。
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 把该类型的外部二进制表示转换为内部表示。如果没有提供这个函数,该类型就不能参与二进制输入。二进制表示的选择应当使其转换为内部形式的代价低廉,同时又具有合理的可移植性。(例如,标准的整数数据类型使用网络字节序作为外部二进制表示,而内部表示使用机器的本机字节序。)接收函数应当执行充分的检查以确保值是有效的。接收函数可以声明为接受一个 internal 类型的参数,或者声明为接受三个类型分别为 internal、oid、integer 的参数。第一个参数是指向一个 StringInfo 缓冲区的指针,该缓冲区保存着接收到的字节串;可选参数与文本输入函数的相同。接收函数必须返回数据类型本身的值。通常,接收函数应当声明为 STRICT;如果不是这样,那么在读取 NULL 输入值时,它将以 NULL 作为第一个参数被调用。在这种情况下,该函数仍然必须返回 NULL,除非它抛出一个错误。(这种情况主要是为了支持域接收函数,域接收函数可能需要拒绝 NULL 输入。)类似地,可选的 send_function 把内部表示转换为外部二进制表示。如果没有提供这个函数,该类型就不能参与二进制输出。发送函数必须声明为接受新数据类型的一个参数。发送函数必须返回 bytea 类型。对于 NULL 值不会调用发送函数。
此时你可能会疑惑:既然输入和输出函数必须在新类型创建之前创建,那它们怎么能被声明为具有新类型的结果或参数呢?答案是,该类型应当首先被定义为一个外壳类型,这是一种除了名称和拥有者之外没有任何属性的占位符类型。这可以通过发出命令 CREATE TYPE (不带任何附加参数)来完成。然后就可以引用外壳类型来定义 C 语言的 I/O 函数。最后,带有完整定义的 nameCREATE TYPE 命令会用一个完整有效的类型定义替换外壳条目,之后新类型就可以正常使用了。
如果类型支持修饰符,则需要可选的 type_modifier_input_function 和 type_modifier_output_function;修饰符是附加在类型声明上的可选约束,例如 char(5) 或 numeric(30,2)。PostgreSQL 允许用户定义的类型接受一个或多个简单常量或标识符作为修饰符。但是,这些信息必须能够被打包成单个非负整数值,以便存储在系统目录中。type_modifier_input_function 会以 cstring 数组的形式接收所声明的修饰符。它必须检查这些值的有效性(如果值不正确则抛出一个错误),如果值正确,则返回一个单一的非负 integer 值,该值将被存储为列 “typmod” 的值。如果类型没有 type_modifier_input_function,类型修饰符将被拒绝。type_modifier_output_function 把内部的整数 typmod 值转换回适合用户显示的正确形式。它必须返回一个 cstring 值,该值就是要追加到类型名称之后的准确字符串;例如 numeric 的函数可能会返回 (30,2)。允许省略 type_modifier_output_function,在这种情况下,默认的显示格式只是用括号括起来的所存储的 typmod 整数值。
可选的 analyze_function 为该数据类型的列执行特定于类型的统计信息收集。默认情况下,如果该类型有默认的 b-tree 操作符类,ANALYZE 将尝试使用该类型的“等于”和“小于”操作符来收集统计信息。对于非标量类型,这种行为很可能不合适,因此可以通过指定一个自定义的分析函数来覆盖它。分析函数必须声明为接受一个 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 66.2 中所述;直接访问它是不明智的。)
可选标志 PASSEDBYVALUE 表示该数据类型的值按值传递,而不是按引用传递。按值传递的类型必须是定长的,并且其内部表示不能超过 Datum 类型的大小(在某些机器上是 4 字节,在另一些机器上是 8 字节)。
alignment 参数指定该数据类型所需的存储对齐。允许的值相当于在 1、2、4 或 8 字节边界上对齐。注意,变长类型的对齐必须至少为 4,因为它们必然包含一个 int4 作为其第一个组件。
storage 参数允许为变长数据类型选择存储策略。(定长类型只允许 plain。)plain 指定该类型的数据总是被内联存储且不压缩。extended 指定系统将首先尝试压缩较长的数据值,如果压缩后仍然太长,则把该值移出主表行。external 允许把值移出主表,但系统不会尝试压缩它。main 允许压缩,但不鼓励把值移出主表。(具有这种存储策略的数据项在没有其他办法让行放得下时仍可能被移出主表,但与 extended 和 external 项相比,它们会被优先保留在主表中。)
所有 storage 值(plain 除外)都意味着该数据类型的函数能够处理已被经过 TOAST 处理的值,如 Section 66.2 和 Section 36.13.1 中所述。给出的其他特定值只是决定可 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 子句携带排序信息。是否实际使用排序信息由操作该类型的函数的实现决定;仅仅把类型标记为可排序并不会自动发生这种事。
每当创建一个用户定义的类型时,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-tree 操作符类的名称。
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 一个现有数据类型的名称,新类型将具有与它相同的表示形式。internallength、passedbyvalue、alignment 和 storage 的值将从该类型复制,除非在此 CREATE TYPE 命令中的其他位置通过显式指定覆盖了它们。
category 该类型的类别代码(单个 ASCII 字符)。默认值为 'U',表示“用户定义类型”。其他标准类别代码可以在 Table 52.65 中找到。你也可以选择其他 ASCII 字符来创建自定义类别。
preferred如果该类型是其类型类别中的首选类型,则为真,否则为假。默认值为假。在现有的类型类别中创建新的首选类型时要非常小心,因为这可能会导致行为发生意想不到的变化。
default该数据类型的默认值。如果省略,则默认为空值。
element正在创建的类型是一个数组;这指定了数组元素的类型。
delimiter在由该类型构成的数组中值之间使用的分隔符字符。
collatable如果该类型的操作可以使用排序信息,则为真。默认值为假。
因为数据类型一旦创建,对其使用就没有任何限制,所以创建基本类型或范围类型相当于向公众授予类型定义中提到的函数的执行权限。对于在类型定义中有用的那几类函数来说,这通常不是问题。但是,如果你设计类型的方式要求在把它转换为外部形式或从外部形式转换回来时使用“机密”信息,那么你可能需要三思。
在 PostgreSQL 8.3 版之前,生成的数组类型的名称总是恰好为元素类型的名称加上一个下划线字符(_)前缀。(因此类型名称的长度被限制为比其他名称少一个字符。)虽然现在通常仍然如此,但在名称达到最大长度或者与以下划线开头的用户类型名称发生冲突的情况下,数组类型的名称可能会与此不同。因此,不推荐编写依赖这种约定的代码。相反,请使用 pg_type.typarray 来找到与给定类型关联的数组类型。
建议避免使用以下划线开头的类型名称和表名称。虽然服务器会更改生成的数组类型名称以避免与用户给定的名称冲突,但仍然存在混淆的风险,特别是对于可能假定以下划线开头的类型名称总是表示数组的旧客户端软件。
在 PostgreSQL 8.2 版之前,不存在外壳类型的创建语法 CREATE TYPE 。创建新基本类型的方法是先创建它的输入函数。在这种方式中,PostgreSQL 会首先把新数据类型的名称视为输入函数的返回类型。在这种情况下,外壳类型会被隐式创建,然后就可以在其余 I/O 函数的定义中引用它。这种方式仍然有效,但已被弃用,并且在将来的某个版本中可能会被禁止。此外,为了避免因函数定义中简单的笔误而意外地让外壳类型弄乱目录,只有当输入函数是用 C 语言编写时才会以这种方式创建外壳类型。 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 的扩展。CREATE TYPE 语句在 SQL 标准中还定义了 PostgreSQL 未实现的其他形式。
创建具有零个属性的复合类型的能力是 PostgreSQL 特有的对标准的偏离(类似于 CREATE TABLE 中的同样情况)。