CREATE TYPE
用于在当前数据库中注册一个新的用户定义数据类型。
简介
CREATE TYPE
用于在当前数据库中注册一个新的用户定义数据类型。执行此命令的用户自动成为新数据类型的拥有者。该命令允许用户为数据库创建具有特定属性和行为的自定义数据结构。
如果在执行CREATE TYPE
命令时指定了一个模式名称,那么新创建的数据类型将位于该指定模式内。如果没有指定模式名称,则该数据类型将默认被创建在当前激活的模式下。
新创建的类型名必须在同一模式内是唯一的,即它不能与该模式中已存在的任何其他类型、域或者表的名称重复。这是因为表在数据库中也被看作是一种数据类型,所以数据类型的名称必须在其所属模式中保持唯一,以避免任何潜在的命名冲突。
CREATE TYPE
命令有五种不同的变体。这五种变体分别用于创建如下类型:组合类型(composite type
)、枚举类型(enumerated type
)、范围类型(range type
)、基础类型(base type
),以及shell
类型。本文将详细讨论前四种类型的创建方法。至于shell
类型,它实质上是一个占位符,用于预留一个名称供将来定义的类型使用。这个占位符可以通过发布一个CREATE TYPE
命令并仅带有类型名称来创建,不需要其他参数。当创建范围类型和基础类型时,shell
类型用作一种前向引用,这样可以在实际定义类型的细节之前就预先声明其存在。
语法
CREATE [ OR REPLACE ] TYPE name AS
( [ attribute_name data_type [ COLLATE collation ] [, ... ] ] );
CREATE TYPE name AS ENUM
( [ 'label' [, ... ] ] );
CREATE TYPE name AS RANGE (
SUBTYPE = subtype
[ , SUBTYPE_OPCLASS = subtype_operator_class ]
[ , COLLATION = collation ]
[ , CANONICAL = canonical_function ]
[ , SUBTYPE_DIFF = subtype_diff_function ]
);
CREATE TYPE name (
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 TYPE name;
组合类型
使用CREATE TYPE
命令的第一种形式可以创建组合类型(composite types
)。这种类型由一组属性名组成,每个属性名都与一个特定的数据类型相关联。如果属性的数据类型支持排序操作,则还可以为每个属性指定一个排序规则。组合类型在本质上与表结构的行类型相似,不过使用CREATE TYPE创建组合类型可以避免实际上创建一个完整的表。
在创建组合类型时,可以选择性地使用OR REPLACE
子句。当包含这个子句时,如果数据库中已经存在一个与你想要创建的组合类型同名的类型,那么现有的类型将会被新定义的类型替换。这样的功能允许你更新一个已存在的组合类型的定义而不需要事先手动删除旧的类型,从而简化了类型维护的过程。
使用OR REPLACE
时应当小心,因为这会覆盖原有类型的定义,可能会影响依赖于该类型的数据库对象。
组合类型可以用作函数的参数类型或返回类型,提供一种定义复杂结构的便捷方式,而不必为此创建一张新的表。组合类型的这种特性使得它在数据库设计中非常灵活且实用,尤其是在需要传递多个值作为一个整体时,或者当函数需要返回多个值而不仅仅是一个简单数据类型的值时。
为了成功创建一个组合类型,创建者必须具备在该组合类型的所有属性数据类型上的USAGE权限。
枚举类型
使用CREATE TYPE
命令的第二种形式可以创建枚举类型(enumerated type
)。枚举类型是由一系列预定义的标签组成,这些标签在创建时必须用引号包围。每个标签的长度不得超过NAMEDATALEN
字节的限制,通常是64字节。
尽管可以创建一个不包含任何标签的枚举类型,但在对该类型使用ALTER TYPE
命令并添加至少一个标签之前,您将无法使用该枚举类型存储任何值。即一个空的枚举类型在添加枚举值之前,无法在实际数据库操作中使用。该特性提供了枚举类型的灵活扩展性,允许数据库设计者在初始定义枚举类型时不必立即确定所有可能的枚举值,而是可以随着应用需求的变化逐渐添加新的枚举值。
范围类型
使用CREATE TYPE
命令的第三种形式可以创建范围类型(range type
)。范围类型允许用户表示一个连续区间,该区间内的值是基于某个子类型定义的。所谓的subtype
是构成范围的基本数据类型,它必须有对应的B树操作符类来决定范围值的顺序。大多数情况下,子类型的默认B树操作符类会被用作排序依据。若需使用非默认操作符类进行排序,可以通过subtype_opclass
选项来指明其名称。如果子类型是可排序的,并且需要应用非默认的排序规则于范围类型值,可以用collation
选项指定相应的排序规则。
创建范围类型时,还可以定义一个可选的canonical
函数,该函数接受一个范围类型的参数并返回同类型的规范化值。这个函数用来将范围值转换为其规范形式。然而,创建canonical
函数有一定难度,因为它需要在范围类型声明前定义好。为了实现这一点,必须先创建一个所谓的shell
类型,这是一种只有名称和所有者而没有具体属性的占位符类型,可以通过执行CREATE TYPE name
命令(不带其他参数)来创建。创建之后,可以使用该shell
类型来声明canonical
函数的参数和返回类型。最后,当声明实际的范围类型时用同样的名称,系统将自动将shell
类型占位符替换为完整定义的范围类型。
另一个可选的功能是定义subtype_diff
函数,它接受两个子类型值作为参数,并返回一个double precision
类型的值来表示两个值之间的差异。虽然这不是必须的,但提供subtype_diff
函数可以提高在该范围类型的列上使用GiST
索引的效率。
基础类型
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
,函数将不会被调用。如果输入函数不是STRICT
,在接收到NULL
输入值时,其第一个参数会是NULL
。在这种情况下,函数应返回NULL
,除非发生错误(这种设计主要是为了支持领域输入函数,它们可能需要拒绝NULL
输入)。
输出函数需要接受一个新数据类型的参数,并将其转换为cstring
类型的返回值。如果值是NULL
,则不会调用输出函数。
可选的receive_function
是处理类型的外部二进制表示,并将其转换为内部格式的函数。如果不提供该函数,则该类型将无法进行二进制输入。二进制表示形式通常更加高效并易于在不同系统之间移植。例如,标准整数数据类型使用网络字节序作为其外部二进制表示,而内部格式则采用机器的本地字节序。接收函数应当进行足够的校验确保输入值是有效的,并且可以接受一个internal
类型的参数,或者三个参数(internal
、oid
、integer
),其中internal
参数指向一个StringInfo
缓冲区,该缓冲区包含接收到的字节串。其余参数与文本输入函数一致。通常,接收函数也应声明为STRICT
。如果不是,那么在接收到NULL
输入时,其第一个参数将会是NULL
,并且函数应该返回NULL
,除非它遇到错误。
类似的,可选的send_function
将内部格式转换为外部二进制表示。如果没有提供该函数,该类型将无法进行二进制输出。发送函数必须接受一个新数据类型的参数,并返回bytea
类型的值。对于NULL
值,发送函数不会被调用。这些函数为用户定义类型提供了与内部数据库系统的交互方式,确保类型能够适应数据库的存储和通信机制。
您可能对如何能够在创建新类型之前声明输入和输出函数这一过程感到疑惑,因为这些函数需要引用尚未创建的新类型作为返回值或参数。解决该问题的方法是先定义一个shell type
,也就是一种仅具有名称和拥有者但没有其他属性的占位符类型。这可以通过执行不携带额外参数的命令CREATE TYPE name
来完成。之后,就可以用C语言编写的输入输出函数引用该shell type
。最终,使用带有完整定义的CREATE TYPE
命令来替换这个shell type
,使其变成一个完整且合法的类型定义,随后新类型就可以正常使用了。
如果该类型支持类型修饰符——即在类型声明上附加的可选约束,例如char(5)
或numeric(30,2)
——则您还需要定义可选的type_modifier_input_function
和type_modifier_output_function
。PolarDB允许用户定义类型接受一个或多个简单常量或标识符作为修饰符。为了在系统目录中存储这些信息,修饰符必须能够被封装进一个非负整数值内。类型修饰符由type_modifier_input_function
接受,该函数以cstring
数组形式接收声明的修饰符,并必须检查其合法性。如果修饰符无效,函数应该抛出错误;如果有效,则返回一个非负整数,该整数将存储在typmod
列中。如果类型没有type_modifier_input_function
,则任何类型修饰符都会被拒绝。
type_modifier_output_function
则是将系统目录中存储的整数typmod
值转换回用户可理解的格式。它必须返回一个cstring
值,这个值是拼接到类型名称后面的字符串。例如,对于numeric
类型,该函数可能会返回(30,2)
。如果默认的显示格式就是只将存储的typmod
整数值放在圆括号内,则可以省略type_modifier_output_function
。这些函数为用户定义的类型提供了一种方式,以实现对类型修饰符的解析和显示,从而在类型声明中允许额外的自定义约束。
可选的analyze_function
用于执行与特定数据类型相关的统计信息收集。这适用于那些列的数据类型。默认情况下,如果该数据类型具有默认的B-树操作符类,ANALYZE
将尝试通过使用类型的equals
和less-than
操作符来收集统计信息。然而,这种方式对于非标量类型并不合适,因此可以通过指定自定义的分析函数来替代默认行为。自定义分析函数需声明单个类型为internal
的参数,并返回boolean
结果。
尽管只有针对新类型创建的I/O
函数和其他函数才了解该类型内部表示的详细信息,但某些关于内部表达的属性必须声明给PolarDB。其中最重要的属性是internallength
。基本数据类型可以是固定长度的(这种情况下internallength
是一个正整数),也可以是可变长度的(将internallength
设置为VARIABLE
,在内部将typlen
设置为-1
表示)。所有可变长度类型的内部表示都必须以一个4字节整数开头,该整数表示该值的总长度。
PASSEDBYVALUE
这一可选的标志,表示新创建的数据类型应该通过值传递而不是通过引用传递。要使用PASSEDBYVALUE
,该类型必须是定长的,且其内部表示的大小不能超过Datum
类型所能容纳的大小限制,这在某些系统上是4字节,在其他系统上可能是8字节。
alignment
参数定义了数据类型的内存对齐要求。可能的值包括按1、2、4或8字节边界对齐。
所有变长类型的alignment
参数至少必须为4,因为变长类型需要在内部包含一个int4
类型的长度值作为它们的首部元素。
storage
参数允许为变长数据类型选择合适的存储策略。对于定长类型,唯一允许的策略是plain
,意味着该类型的数据总是存储在行内,并且不会进行压缩。extended
策略表示系统将尝试对过长的数据值进行压缩,并在必要时将数据移到主表行之外。external
策略允许将数据移出行,但系统不会尝试压缩。main
策略虽然允许压缩,但不鼓励将数据移出主表行,只有在没有其他方式能使行大小合适的情况下,带有这种存储策略的数据才会被移出,而它会优先于extended
和external
策略的数据保留在行内。
除了plain
之外,所有的storage
选项都隐含该数据类型的函数能够处理TOAST
(The Oversized-Attribute Storage Technique
,超大属性存储技术)过的值。这里指定的值只是决定一种可TOAST
数据类型列的默认TOAST
存储策略,用户仍可以使用ALTER TABLE SET STORAGE
来为列选择其他策略。
like_type
参数提供了一种基于现有类型来定义新类型基本属性的方法:直接复制一个已存在的类型的属性。internallength
、passedbyvalue
、alignment
和storage
这些属性的值将从指定的类型中复制过来(虽然这些属性的值可以在LIKE
子句中被覆盖,但这通常是不必要的)。当新类型的底层实现实际上是以某个现有类型作为承载体的时候,这种方法来指定类型的属性特别有用。
category
和preferred
参数提供了助力,用于在存在模棱两可的情形下决定应用哪种隐式类型转换。每种数据类型都被分配到以单个ASCII
字符命名的类别中,同时每种类型都有可能成为其所属类别中的首选类型。当需要消除重载函数或操作符的歧义时,解析器将优先考虑转换为首选类型(但仅限于同一类别内的类型转换)。对于那些既不隐式转换为任何其他类型,也不接受从任何其他类型隐式转换过来的类型,可以保持这些设置的默认值。然而,对于一组具有隐式转换关系的相关类型,标记它们属于同一类别并选择一种或两种最常用的类型作为该类别的首选通常是非常有帮助的。特别是在将一种用户定义的类型加入到一个现有的内建类别中(例如数字或字符串类别)时,category
参数非常有用。自然也可以创建一个全新的、完全由用户定义的类型所组成的类别。对于这类新类别,可以选择任何非大写字母的ASCII字符作为标识。
若用户希望数据类型的列在默认情况下具有某个非空值,可以指定默认值。默认值可通过DEFAULT
关键词来指定(这可以被附加到特定列上的显式DEFAULT
子句覆盖)。
要定义一种数据类型为数组类型,可使用ELEMENT
关键词来指明该数组的基本元素类型。例如,要定义一种基于4字节整数int4
的数组类型,应指定ELEMENT = int4
。
为了指定分隔这种类型数组在外部表示中的值的定界符,delimiter
可设置为特定字符。默认的定界符是逗号(,
)。
定界符与数组元素类型有关,而非数组类型本身。
如果collatable
这一可选的布尔参数被设置为真,该种类型的列定义和表达式就可以通过使用COLLATE
子句来携带排序规则信息。实施该类型操作的函数需要负责实际利用这些信息。仅仅将类型标记为可排序,并不意味着它们会自动使用这类信息。
数组类型
在PolarDB中,一旦用户定义了一种新的数据类型,系统会自动创建相应的数组类型。这个自动生成的数组类型的名称是由原始元素类型的名称前加一个下划线来构成的。如果这样组成的名称长度超出了NAMEDATALEN
字节的限制,则名称会被自动截断。如果截断后的名称与现有类型的名称发生冲突,系统会尝试其他的名称,直至找到一个不会造成冲突的名字。
这种隐式创建的数组类型是变长的,它使用内建的输入和输出函数array_in
以及array_out
。这个数组类型将会跟随其元素类型的所有权变动或模式变动而相应地进行更改。如果其元素类型被删除,那么这个数组类型也会随之被删除。这样的设计确保了类型系统的一致性和简洁性,同时避免了用户手动创建和管理数组类型所需的额外工作。
尽管在PolarDB中系统会自动创建与用户定义类型对应的数组类型,ELEMENT
选项的实际用途出现在一个特定场景中:当您创建的是一种定长类型,并且这种类型内部本质上是多个相同元素的数组时。假设您不仅希望为这种类型提供整体的操作,还想允许通过下标来直接访问数组内的各个元素。例如,point
类型内部包含两个浮点数,通过point[0]
和point[1]
可以直接访问这两个坐标值。
这种通过下标访问的功能仅适用于其内部结构确实为一系列定长字段的定长类型。而对于那些具有可变长度的类型,他们必须要有一个通用化的内部表达,这样才能使用array_in
和array_out
函数来支持下标访问。由于某些历史遗留问题,定长数组类型的下标是从零开始的,这与变长数组类型从一开始的下标计数方式不同。
参数
参数名称 | 说明 |
| 要创建的类型的名称(可以被模式限定)。 |
| 组合类型的一个属性(列)的名称。 |
| 组合类型中一列的现有数据类型的名称。 |
| 与组合类型的某列或范围类型相关联的现有排序规则的名称。 |
| 文本字符串,表示枚举类型中某个特定值的文本标签。在枚举类型定义中,每个值都通过一个标签进行唯一表示。 |
| 用于在范围类型中指定范围类型的元素类型的名称。范围类型代表此元素类型的值的范围。 |
| 范围类型的元素类型所使用的B树操作符类的名称。 |
| 范围类型的规范化函数名称。 |
| 差函数是用于计算范围类型中两个元素之间差异的函数名称。 |
| 将数据从类型的外部文本表现形式转换为数据库内部使用的形式。 |
| 与 |
| 将数据从类型的外部二进制表现形式转换为数据库内部使用的形式。 |
| 将数据从类型的内部表现形式转换为外部二进制表现形式。 |
| 将类型的修饰符数组转换为内部形式的函数名。 |
| 将类型的修饰符的内部形式转换为外部文本形式的函数名。 |
| 对应数据类型执行统计分析的指定函数名。 |
| 一个数值常量,用于指定新类型内部表示的字节长度,默认其长度是可变的。 |
| 该数据类型的存储对齐要求。取值为: |
| 确定数据类型的存储策略。取值为: |
| 一个现有数据类型的名称,它与新类型具有相同的内部表示。可以从这个类型复制 |
| 该数据类型的类别码(一个 |
| 如果这种类型是其类别中的首选类型,则值为真,否则为假。默认值为假。在一个已有的类型类别中新增优先类型时需要十分谨慎,因为这可能引发意外的行为改变。 |
| 表示数据类型的默认值。如果未指定,默认为空。 |
| 如果创建的类型是数组,此处指定数组元素的类型。 |
| 在由这个类型组成的数组中,用于分隔值的定界符。 |
| 如果这种类型的操作能够使用排序规则信息,则值为真。默认为假。 |
说明
建议在命名类型和表时避免使用以下划线开头的名称。虽然数据库具有机制来修改自动生成的数组类型的名称,以防止与用户定义的名称产生冲突,但仍然存在潜在的混淆风险。以下划线开头的名称通常被保留用于数据库系统自身生成的名称,如数组类型名称的约定,这些名称是由基础类型名称加上前导下划线构成的。因此,为了减少不必要的混淆并保持命名清晰一致,最好避免这种命名做法。
示例
创建一个组合类型,然后在随后的函数定义中将其作为返回类型使用:
CREATE TYPE compfoo AS (f1 int, f2 text);
CREATE FUNCTION getfoo() RETURNS SETOF compfoo AS $$
SELECT fooid, fooname FROM foo
$$ LANGUAGE SQL;
创建一个新的组合类型的过程,并在创建后对这个类型的定义进行了更新:
CREATE TYPE compfoo AS (f1 int, f2 text);
CREATE OR REPLACE TYPE compfoo AS (f2 text, f1 int);
定义一个枚举类型,并将其应用于新建表的定义中:
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
);
创建一个大对象类型并且将它用在了一个表定义中:
CREATE TYPE bigobj (
INPUT = lo_filein, OUTPUT = lo_fileout,
INTERNALLENGTH = VARIABLE
);
CREATE TABLE big_objs (
id integer,
obj bigobj
);