58.3. 扩展性

GIN接口有一个高层次的抽象,仅要求实现被访问数据类型的语义即可。 GIN层自身可以处理并发操作、记录日志、搜索树结构。

定义一个GIN访问方法所要做的所有事情就是实现几个用户定义的方法, 这些方法定义了键在树中的行为、键与键之间的关系、被索引的项目、能够使用索引的查询。 简而言之,GIN将扩展性与普遍性、代码重用、清晰的接口结合在了一起。

有三个方法是GIN索引操作符类必须提供的:

int compare(Datum a, Datum b)

比较两个键(不是被索引的项目!)然后返回一个小于、等于或大于零的值,分别表示第一个键小于、等于或大于第二个键。 NULL的键永远不会被传入这个函数。

Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)

给定一个被索引的项目,返回一个对应的由palloc分配的键的数组。返回的键的数目必须存储在*nkeys中。 如果任何键可能为NULL,还要palloc一个包含*nkeysbool元素的数组, 将地址存储到*nullFlags,并且根据需要设置NULL值。 如果所有键都是非NULL的,可以让*nullFlags保持为NULL(它的初始值)。 如果输入的项目不包含任何键,返回值可以为NULL

Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)

给定一个被查询的值,返回一个对应的palloc分配的键数组。 也就是说,query是可索引操作符右侧的值,而该操作符左侧是被索引的字段。 n是操作符类中的操作符策略号(参见第 35.14.2 节)。 通常,extractQuery需要考量n来决定query的数据类型以及提取键值的方法。 返回的数组的元素个数必须存放在*nkeys中。 如果任何键可能为NULL,还要palloc一个包含*nkeysbool元素的数组, 将地址存储到*nullFlags,并且根据需要设置NULL值。 如果所有键都是非NULL的,可以让*nullFlags保持为NULL(它的初始值)。 如果query不包含任何键,返回值可以为NULL

searchMode是一个输出参数,它允许extractQuery指定一些关于如何执行搜索的细节。 如果*searchMode被设置成GIN_SEARCH_MODE_DEFAULT(这也是调用函数前它被初始化的值), 只有匹配至少一个返回的键才能被认为是候选的匹配。 如果*searchMode被设置成GIN_SEARCH_MODE_INCLUDE_EMPTY, 除了包含至少一个匹配的键的项目,根本不包含任何键的项目也被视为候选的匹配。 (这个模式对于实现像“是否是子集”这样的操作是有用的) 如果*searchMode被设置成GIN_SEARCH_MODE_ALL, 索引中所有非NULL的项目都被认为是候选的匹配,不管它们是否匹配返回的键中的任何一个。 (这个模式比起其它两个要慢很多,因为它必须要扫描整个索引,但这对正确的实现边界条件可能是必要的。 一个需要这种模式的操作符在大多数时候很可能不是一个好的GIN操作符类的候选。) 用于设置这个模式的符号定义在access/gin.h中。

pmatch是在部分匹配时需要用到的一个输出参数。 如果使用它,extractQuery必须分配一个有*nkeys个布尔值的数组, 并把数组地址保存到*pmatch。 数组的每个元素应该被设置为:TRUE,如果相应的键需要部分匹配;或者FALSE,如果不是。 如果*pmatch被设置为NULL,GIN假设不需要部分匹配。 在函数调用前这个值被初始化成了NULL, 因此,对于不支持部分匹配的操作符类,可以简单的忽略这个参数。

extra_data是一个允许extractQuery传递额外数据给consistentcomparePartial的输出参数。 如果使用它,extractQuery必须分配一个包含*nkeys个Pointer元素的数组, 并把数组地址保存到*extra_data,然后把它想附加的东西存储到各个独立的指针中。 在函数调用前这个值被初始化成了NULL, 因此,对于不需要附加数据的操作符类,可以简单的忽略这个参数。 如果*extra_data被设置了,那么整个数组会被传给consistent方法, 适当的元素会被传给comparePartial方法。

一个操作符类必须也提供一个函数,检查索引了的条目是否匹配查询。它有两种方式, 一个布尔consistent函数和一个三元triConsistent函数。 triConsistent包含两种功能,所以单独提供triConsistent就足够了。 不过,如果布尔变体计算的成本显著更低,那么提供两者是有利的。 如果只提供了布尔变体,那么禁用一些依赖于在抓取所有秘钥之前驳斥索引条目的最优化。

bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])

如果被索引项目满足策略号为n的查询操作符(或可能满足,如果recheck指示符被返回了的话)返回TRUE。 这个函数并不直接访问被索引项目的值,因为GIN并没有精确的把项目保存下来, 但是需要知道哪些从查询中提取的键值出现在给定的索引项目中。 check数组的长度是nkeys,这与先前针对这个query调用的extractQuery函数返回的键值的数目相同。 如果被索引项目包含了相应的查询键,check数组中对应的元素值就是TRUE。 比如,如果(check[i] == TRUE),那么意味着extractQuery的结果数组的第i个键出现在了索引项目中。 考虑到consistent可能会用到,原始的query也被作为参数传入进来。 与此相同的还有extractQuery函数返回的queryKeys[]nullFlags[]extra_dataextractQuery函数返回的额外数据数组,如果没有的话就是NULL

extractQueryqueryKeys[]中返回一个NULL的键值, 如果被索引项目包含NULL键值,相应的check[]中的元素是TRUE。 也就是说,check[]的语义很像IS NOT DISTINCT FROM。 如果需要知道是通常值匹配还是NULL匹配,consistent函数可以检查相应的nullFlags[]元素。

成功执行后,如果对这个元组需要执行查询操作符是否匹配的再检查,*recheck需要被设置为TRUE, 如果索引测试已经是精确的了,则设为FALSE。 也就是说,FALSE的返回值确保堆元组不匹配这个查询; 伴随*recheck为FASLE的TRUE的返回值确保堆元组匹配这个查询; 伴随*recheck为TRUE的TRUE的返回值意味着堆元组可能匹配这个查询, 因此需要取得这个堆元组,并通过直接针对原始的被索引项目评估查询操作符的方式进行再检查。

GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])

triConsistent类似于consistent, 但是不是一个布尔check[],对于每一个键有三个可能的值: GIN_TRUEGIN_FALSEGIN_MAYBEGIN_FALSEGIN_TRUE作为普通的布尔值有相同的含义。 GIN_MAYBE意味着该键的存在是未知的。当出现GIN_MAYBE值时, 如果该条目匹配,则该函数都应该只返回GIN_TRUE,不管索引条目是否包含对应的查询关键字。 类似的,该函数必须只在该条目不匹配时返回GIN_FALSE,不管它是否包含GIN_MAYBE关键字。 如果结果依赖于GIN_MAYBE项,也就是,该匹配不能确认或驳回了基于已知的查询关键字, 那么该函数必须返回GIN_MAYBE。

check矢量中没有GIN_MAYBE值时,GIN_MAYBE 返回值等价于在布尔consistent函数中设置recheck标志。

GIN操作符类可以可选地提供下列方法:

int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)

比较一个部分匹配查询键和一个索引键。 返回一个整形值,它个符号代表了不同的含义:小于0意味着索引键不匹配查询,但是索引扫描应该继续; 0意味着索引键匹配查询;大于0指示应该终止索引扫描,因为不可能再有更多的匹配。 这里提供了生成部分一致查询的操作符的策略号n,以防需要用它的语义去决定何时终止扫描。 同样的,extra_dataextractQuery生成的额外数据数组中的相应元素,或者为NULL,如果没有的话。 NULL的键永远不会被传入这个函数。

为了支持"部分匹配"查询,一个操作符类必须提供comparePartial方法, 并且当遇到部分匹配查询时,它的extractQuery方法必须设置pmatch参数。 详细请参考第 58.4.2 节

上面的各种Datum值的实际数据类型根据操作符类的不同而不同。 传入到extractValue中的项目值总是操作符类的输入类型, 所有的键值类型必须这个类的STORAGE类型。 传入到extractQueryconsistentconsistentquery参数的类型 是由策略号识别的类成员操作符的右操作数的输入类型。 它不需要和项目类型相同,只要可以从中抽取出正确类型的键值。