64.3. B-树支持函数 #
如表 38.9 所示,B-树定义了一个必需和四个可选的支持函数。五个用户定义的方法如下:
order对于 B-树操作符族为其提供比较操作符的每一种数据类型组合,都必须提供一个比较支持函数。该函数在
pg_amproc中注册,支持函数编号为 1,并且amproclefttype/amprocrighttype要等于该比较的左右数据类型(也就是与匹配操作符在pg_amop中注册时相同的数据类型)。比较函数必须接受两个非 null 值A和B,并返回一个int32值,其值为<0、0或>0,分别对应A<B、A=B或A>B。不允许返回 null:该数据类型的所有值都必须可比较。示例见src/backend/access/nbtree/nbtcompare.c。如果参与比较的值属于支持排序规则的数据类型,则会通过标准的
PG_GET_COLLATION()机制,把适当的排序规则 OID 传递给比较支持函数。sortsupport可选地,B-树操作符族可以提供排序支持函数,注册为支持函数编号 2。这些函数允许以比朴素地直接调用比较支持函数更高效的方式,实现用于排序的比较。相关 API 定义在
src/include/utils/sortsupport.h中。in_range可选地,B-树操作符族可以提供 in_range 支持函数,注册为支持函数编号 3。这些函数不会在 B-树索引操作期间使用;相反,它们会扩展操作符族的语义,使其能够支持包含
RANGEoffsetPRECEDING和RANGEoffsetFOLLOWING这两种帧边界类型的窗口子句(见第 4.2.8 节)。本质上,它额外提供的信息是:如何以与该族数据排序兼容的方式,对offset值进行加减。in_range函数必须具有如下签名:in_range(
valtype1,basetype1,offsettype2,subbool,lessbool) returns boolval和base必须具有相同类型,并且该类型必须是操作符族支持的类型之一(也就是该族为其提供排序的一种类型)。不过,offset可以是另一种类型,而该类型甚至可能不被该族以其他方式支持。例如,内置的time_ops族就提供了一个in_range函数,其offset的类型为interval。一个族可以提供in_range函数,分别对应其支持的任意类型以及一个或多个offset类型。每个in_range函数都应在pg_amproc中登记,其中amproclefttype等于type1,而amprocrighttype等于type2。in_range函数的核心语义取决于这两个布尔标志参数。它应当先对base与offset做加法或减法,再把val与结果比较,具体如下:如果
!sub且!less,返回val>=(base+offset)如果
!sub且less,返回val<=(base+offset)如果
sub且!less,返回val>=(base-offset)如果
sub且less,返回val<=(base-offset)
在此之前,函数应检查
offset的符号:如果它小于零,就引发错误ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE(22013),错误文本类似于“invalid preceding or following size in window function”。(这是 SQL 标准的要求,尽管非标准操作符族或许会选择忽略这一限制,因为从语义上看似乎并没有太大必要。)把这个要求委托给in_range函数,是为了让核心代码无需理解对于某个特定数据类型来说“小于零”究竟意味着什么。另外还希望
in_range函数在可行时避免因为base+offset或base-offset会溢出而抛出错误。即便该值会超出该数据类型的取值范围,仍然可以判定正确的比较结果。注意,如果该数据类型包含诸如“infinity”或“NaN”之类的概念,就可能需要格外小心,以确保in_range的结果与该操作符族的正常排序顺序一致。in_range函数的结果必须与操作符族施加的排序顺序保持一致。更精确地说,给定任意固定的offset和sub值,则:如果
in_range在less= true 时,对某个val1和base返回真,那么对于每一个val2<=val1,只要其base相同,它也必须返回真。如果
in_range在less= true 时,对某个val1和base返回假,那么对于每一个val2>=val1,只要其base相同,它也必须返回假。如果
in_range在less= true 时,对某个val和base1返回真,那么对于每一个base2>=base1,只要其val相同,它也必须返回真。如果
in_range在less= true 时,对某个val和base1返回假,那么对于每一个base2<=base1,只要其val相同,它也必须返回假。
当
less= false 时,也有条件相反的对应陈述成立。如果被排序的类型(
type1)属于支持排序规则的数据类型,则会通过标准的 PG_GET_COLLATION() 机制,把适当的排序规则 OID 传递给in_range函数。in_range函数不必处理 NULL 输入,并且通常会被标记为 strict。equalimage可选地,B-树操作符族可以提供
equalimage(“相等蕴含映像相等”)支持函数,注册为支持函数编号 4。这些函数允许核心代码判断何时可以安全地应用 B-树去重优化。目前,equalimage函数只会在构建或重建索引时被调用。equalimage函数必须具有如下签名:equalimage(
opcintypeoid) returns bool返回值是关于某个操作符类及其排序规则的静态信息。返回
true表示可以保证:该操作符类的order函数只有在其A和B参数也能够互换而不损失任何语义信息时,才会返回0(“参数相等”)。如果未注册equalimage函数,或其返回false,就表示不能假定该条件成立。opcintype参数是该操作符类所索引数据类型的。这只是为了方便在不同操作符类之间复用同一个底层pg_type.oidequalimage函数。如果opcintype是支持排序规则的数据类型,则会通过标准的PG_GET_COLLATION()机制,把适当的排序规则 OID 传递给equalimage函数。就操作符类本身而言,返回
true表示可以安全去重(或者更准确地说,对于传递给其equalimage函数的那个排序规则来说可以安全去重)。但是,只有当每个被索引列都使用注册了equalimage函数的操作符类,并且各函数在调用时实际都返回true时,核心代码才会认为某个索引可以安全地去重。映像相等与简单的按位相等几乎是同一个条件。两者有一个细微差别:当 varlena 数据类型建立索引时,由于输入时应用 TOAST 压缩可能不一致,两个映像相等的 datum 的磁盘表示未必按位相等。形式化地说,当某个操作符类的
equalimage函数返回true时,就可以安全地假定datum_image_eq()C 函数将始终与该操作符类的order函数保持一致(前提是向equalimage和order函数传入的是同一个排序规则 OID)。核心代码本质上无法仅根据同一多数据类型族中其他操作符类的细节,推断出某个操作符类的“相等蕴含映像相等”状态。此外,操作符族注册跨类型的
equalimage函数并不合理,尝试这样做会导致错误。原因在于,“相等蕴含映像相等”状态并不只依赖于排序/相等语义,而这些语义大致是在操作符族层面定义的。一般来说,某个具体数据类型所实现的语义必须单独考虑。核心 PostgreSQL 发行版中包含的操作符类遵循的惯例是:注册一个现成的通用
equalimage函数。大多数操作符类注册btequalimage(),这表示去重在无条件下都是安全的。像text这样支持排序规则的数据类型的操作符类会注册btvarstrequalimage(),这表示在确定性排序规则下去重是安全的。第三方扩展的最佳实践则是注册它们自己的自定义函数,以保留控制权。options可选地,B-树操作符族可以提供
options(“操作符类特定选项”)支持函数,注册为支持函数编号 5。这些函数定义一组用户可见的参数,用于控制操作符类行为。options支持函数必须具有如下签名:options(
reloptslocal_relopts *) returns void该函数会接收一个指向
local_relopts结构体的指针,必须在其中填入一组操作符类特定选项。其他支持函数可以使用PG_HAS_OPCLASS_OPTIONS()和PG_GET_OPCLASS_OPTIONS()宏来访问这些选项。目前还没有任何 B-树操作符类带有
options支持函数。B-树不像 GiST、SP-GiST、GIN 和 BRIN 那样允许键具有灵活的表示方式。因此,options在当前的 B-树索引访问方法中大概没有太多用途。尽管如此,为了统一性,这个支持函数还是被加入到了 B-树中,并且很可能会在 PostgreSQL 中 B-树的进一步演进过程中派上用场。
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.