64.3. B-树支持函数

表 38.9中所示,btree定义了一种必需的和四种可选的支持函数。这五种用户定义的方法为:

order

对于btree操作符族为其提供了比较操作符的每一种数据类型组合,操作符族必须提供一个比较支持函数,在pg_amproc中注册:支持函数编号为1,amproclefttype/amprocrighttype等于比较的左右数据类型(即匹配的操作符注册在pg_amop中的数据类型)。 比较函数必须接收两个非空值AB并且返回一个int32值,返回值在A < BA = B以及A > B时分别为< 00> 0。 不允许空值结果:该数据类型的所有值必须是可比较的。 例子请见src/backend/access/nbtree/nbtcompare.c

如果被比较的值是一种可排序的数据类型,合适的排序规则OID将使用标准的PG_GET_COLLATION()机制被传递给比较支持函数。

sortsupport

可选地,btree操作符族可以提供排序支持函数,它们以支持函数编号2注册。 这些函数允许以一种比单纯调用比较支持函数更加高效的方式实现排序比较。 涉及的API在src/include/utils/sortsupport.h中定义。

in_range

可选地,btree操作符族可以提供in_range支持函数,它们以支持函数编号3注册。 在btree索引操作期间不会用到这些函数,它们扩展了操作符族的语义,这样就能支持包含RANGE offset PRECEDING以及RANGE offset FOLLOWING窗口帧界类型(见第 4.2.8 节)的窗口子句。 归根到底,这些函数所提供的额外信息是如何以一种与该操作符族的数据排序相兼容的方式加上或者减去一个offset值。

An in_range 函数必须有签名

in_range(val type1, base type1, offset type2, sub bool, less bool)
returns bool

valbase必须是同一种类型,该类型也是操作符族所支持的类型之一(即它提供排序的一种类型)。 不过,offset可以是一种不同的类型,该类型有可能不被该操作符族所支持。例如内建的time_ops族提供了一个in_range函数,其offset是类型interval。 一个操作符族可以为其所支持的任意类型提供in_range函数以及一个或者更多种offset类型。 每一个in_range函数在进入到pg_amproc时,需要有amproclefttype等于type1以及amprocrighttype等于type2

in_range函数的本质语义取决于两个布尔标志参数。 它应该将baseoffset相加或者相减,然后用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函数,这样核心代码不需要理解对一种特定数据类型less than zero表示什么。

一个额外的期望是,如果可行,in_range函数应当在base + offset或者base - offset溢出时避免抛出错误。 即便值超过了该数据类型的范围,也可以确定正确的比较结果。 注意,如果数据类型包括诸如infinity或者NaN之类的概念,就需要额外的注意确保in_range的结果符合该操作符族的正常排序顺序。

in_range函数的结果必须与操作符族施加的排序顺序保持一致。 准确的来说,给定任意固定的offset值以及sub值,那么:

  • 如果带有less = true的in_range对某个val1base为真,则它必须对每一个有相同baseval2 <= val1为真。

  • 如果带有less = true的in_range对某个val1base为假,则它必须对每一个有相同baseval2 >= val1为假。

  • 如果带有less = true的in_range对于某些valbase1为真,那么它对于每一个有相同valbase2 >= base1也必须为真。

  • 如果带有less = true的in_range对于某些valbase1为假, name它对于每一个有相同valbase2 <= base1也必须为假。

less = false时,类似的具有相逆条件的语句成立。

如果被排序的类型(type1)是可排序的,合适的排序规则OID将使用标准的PG_GET_COLLATION()机制被传递给in_range函数。

in_range函数不需要处理NULL输入,并且通常将被标记为strict。

equalimage

可选地,btree 操作符系列可以提供equalimageequality implies image equality)支持功能,注册在支持函数 4 下面。 这些函数允许核心代码确定何时可以安全地应用 btree 重复数据消除进行优化。 目前,equalimage函数仅在生成或重建索引时调用。

一个 equalimage 函数必须包含签名

equalimage(opcintype oid) returns bool

返回值是有关运算符类和排序规则的静态信息。 返回true表示运算符类的 order 函数保证仅返回0 (arguments are equal) 时,AB参数也可以互换,而不会丢失任何语义信息。 不注册equalimage函数或返回false表示不能假定此条件成立。

opcintype参数是操作符类索引的数据类型的pg_type.oid。 这提供一种便利,允许在操作符类之间重用相同的底层equalimage函数。 如果opcintype是可排序数据类型,则相应的排序规则 OID 将传递给 equalimage 函数,使用标准的 PG_GET_COLLATION() 机制。

就操作符类而言,返回true意味着重复数据删除是安全的(或安全排序规则,其OID传递给它的equalimage函数)。 但是,核心代码只有在every 索引列使用注册了 equalimage 函数的操作符类时,并且每个函数在调用时实际返回 true 时,才会认为重复数据删除是安全的。

图像相等几乎与简单位元(simple bitwise)相等的条件相同。 有一个细微的区别:在索引 varlena 数据类型时,由于输入上TOAST压缩应用不一致,两个图像相等基准的磁盘表示可能不是位相等的。 从形式上看,当操作符类的 equalimage函数返回 true 时,可以安全的地假定 datum_image_eq() C 函数将始终与操作符类的 order 函数一致(前提是将同一个排序规则 OID 传递给 equalimageorder 函数)。

核心代码从根本上无法推断出任何关于基于同一系列中其他运算符类的详细信息的具有多数据类型系列中的操作符类的equality implies image equality状态。 此外,操作符系列注册跨类型 equalimage函数是不明智的,尝试这样做将导致错误。 这是因为equality implies image equality状态不仅仅取决于排序/相等语义,这些语义或多或少在操作符系列级别定义。 通常,特定数据类型实现的语义必须单独考虑。

核心 PostgreSQL 分发中包含的操作符类所遵循的约定是注册股票(register a stock),通用equalimage函数。 大多数操作符类注册btequalimage(),这表明重复数据删除是安全的。 可排序数据类型的操作符类(如 text注册btvarstrequalimage(),这表明在确定性排序规则下应用重复数据删除是安全的。 第三方扩展的最佳实践是注册自己的自定义函数以保持控制。

options

可选地,B 树操作符系列可以提供options操作符类指定选项)支持功能,注册在支持函数 5 下面。 这些函数定义一组用户可见的参数,用于控制操作符类行为。

options 支持函数必须具有签名

options(relopts local_relopts *) returns void

函数传递一个指向local_relopts结构的指针,需要用一组操作符类特定的选项来填充。 这些选项可以通过PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏,从其他支持的函数进行访问。

目前,没有 B-Tree 操作符类具有 options支持功能。 B-tree不允许灵活表示键, 例如 GiST, SP-GiST, GIN 和 BRIN。 因此,options在当前的B-tree索引访问方法中可能没有太多的应用。 不过,此支持函数已添加到 B-tree中以保持一致性,并且可能会在PostgreSQL中B-tree的进一步演进中找到更多应用。