67.3. 可扩展性

GIN接口有一个高层次的抽象,要求访问方法实现者只需要实现数据类型被访问的语义。GIN层本身会操心并发、日志和搜索树结构的事情。

要让一个GIN访问方法工作起来所要做的全部事情就是实现一些用户定义的方法,它们定义了树中键的行为以及键、被索引项以及可索引查询之间的关系。简而言之,GIN的可扩展性结合了通用性、代码重用和一个干净的接口。

一个用于GIN的操作符类必须提供的两种方法是:

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

给定一个要被索引的项,返回一个 palloc 过的键的数组。被返回的键的数量必须被存储在*nkeys中。如果键中的任意一个可能为空,还要 palloc 一个*nkeysbool域的数组,将它的地址存储在*nullFlags中,并且根据需要设置这些空值标志。如果所有的键都非空,*nullFlags可以被留成NULL(其初始值)。如果该项不包含键,返回值可以为NULL

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

给定一个要被查询的值,返回一个 palloc 过的键的数组。即query是一个可索引操作符(左手边是被索引列)的右手边的值。n是操作符类中操作符的策略号(见第 38.16.2 节)。通常,extractQuery将需要参考n来判断query的数据类型以及它应该用什么方法来抽取键值。被返回的键的数量必须被存储在*nkeys中。如果键中的任意一个可能为空,还要 palloc 一个*nkeysbool域的数组,将它的地址存储在*nullFlags中,并且根据需要设置这些空值标志。如果所有的键都非空,*nullFlags可以被留成NULL(其初始值)。如果该项不包含键,返回值可以为NULL

searchMode是一个输出参数,它允许extractQuery指定有关搜索如何被完成的细节。如果*searchMode被设置为GIN_SEARCH_MODE_DEFAULT(这是在被调用之前它被初始化的值),只有那些匹配至少一个被返回键的项才会被考虑作为候选匹配。如果*searchMode被设置为GIN_SEARCH_MODE_INCLUDE_EMPTY,那么除了至少包含一个匹配键的项之外,根本不包含键的项也被考虑作为候选匹配(例如,这种模式对于实现“是...的子集”操作符有用)。如果*searchMode被设置为GIN_SEARCH_MODE_ALL,那么索引中所有非空项都被考虑作为候选匹配,不管它们是否匹配被返回的键(这种模式比其他两种选择要慢很多,但是它对于正确实现极端情况可能是必要的。需要这种模式的操作符在大部分情况下可能并不是一个 GIN 操作符类的好选择)。用于设置这个模式的符号被定义在access/gin.h中。

pmatch是一个输出参数,它用于在部分匹配匹配被支持时使用。要用它,extractQuery必须分配一个*nkeys个布尔值的数组,并且把它的地址存储在*pmatch中。如果一个键要求部分匹配,该数组的对应元素应该被设置为 TRUE,否则设置为 FALSE。如果*pmatch被设置为NULL,则 GIN 假定不需要部分匹配。在调用前,该变量被初始化为NULL,这样这个参数可以简单地被不支持部分匹配的操作符类忽略。

extra_data是一个输出参数,它允许extractQuery传递额外数据给consistentcomparePartial方法。要用它,extractQuery必须分配一个*nkeys个指针的数组,并且把它的地址存储在*extra_data中,然后把任何它想存储的东西存到单个指针中。在调用前该变量被初始化为NULL,这样这个参数可以简单地被不需要额外数据的操作符类忽略。如果*extra_data被设置,整个数组被传递给consistent方法,并且适当的元素会被传递给comparePartial方法。

一个操作符类必须提供一个函数检查一个被索引的项是否匹配查询。有两种形式, 一个布尔函数consistent,以及一个三元函数triConsistenttriConsistent覆盖了两者的功能,因此提供triConsistent一个足矣。但是, 如果布尔变体的计算代价要更低,两者都提供就会有好处。如果只提供布尔变体, 一些基于在取得所有键之前拒绝索引项的优化将会被禁用。

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

如果一个被索引项满足(如果重新检查指示被返回,则表示可能满足)有策略号n的查询操作符,则返回 TRUE。这个函数并没有直接访问被索引项的值,因为GIN没有显式存储项。可用的是关于哪些从查询抽取出的键值出现在一个给定被索引项中的知识。check数组的长度是nkeys,它和前面由extractQuery为这个查询数据返回的键的数目相同。 如果被索引项包含一个查询键,那么check数组的对应元素为 TRUE,即如果 (check[i] == TRUE) ,则extractQuery结果数组的第 i 个键存在于被索引项中。在consistent方法需要参考原始query数据的情况中,它会被传递进来,前面由extractQuery返回的queryKeys[]nullFlags[]数组也一样。extra_data是由extractQuery返回的额外数据数组,如果没有额外数据则为NULL

extractQueryqueryKeys[]中返回一个空值键时,如果被索引项包含一个空值键则对应的check[]元素为 TRUE。即,check[]的语义类似IS NOT DISTINCT FROM。如果consistent函数需要说出一个常规值匹配和一个空值匹配之间的区别,它可以检查对应的nullFlags[]元素。

在成功时,如果堆元组需要根据查询操作符被重新检查,则*recheck应该被设置为 TRUE,或者如果索引测试是准确的则设置为 FALSE。即,一个 FALSE 返回值保证堆元组不匹配查询;一个 TRUE 返回值以及设置为 FALSE 的*recheck保证堆元组匹配查询;并且一个 TRUE 返回值和设置为 TRUE 的*recheck表示堆元组可能匹配查询,因此它需要被取出并且通过在原始的被索引项上计算查询操作符来重新检查。

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_MAYBE), 该函数必须只返回GIN_FALSE。 如果结果依赖于GIN_MAYBE项,即无法根据已知查询键确认或拒绝匹配, 该函数必须返回GIN_MAYBE

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

此外,GIN必须有方法能排序存储在索引中的键值。操作符类可以通过指定一种比较方法来定义排序顺序:

int compare(Datum a, Datum b)

比较两个键(不是被索引项!)并且返回一个整数,该整数为小于零、等于零或者大于零分别表示第一个键小于、等于或者大于第二个键。空值键绝不会被传递给这个函数。

或者,如果操作符类不提供compare方法,GIN将查看索引键数据类型的默认btree操作符类,并且使用它的比较函数。推荐为只用于一种数据类型的GIN操作符类指定这个比较函数,因为查找btree操作符类需要消耗一些周期。不过,多态的GIN操作符类(例如array_ops)通常无法指定单一的比较函数。

一个用于GIN的操作符类可以选择性的提供下列方法:

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

比较一个部分匹配键和一个索引键。返回一个整数,其符号指示结果:小于零表示索引键不匹配查询,但是索引扫描应该继续;零表示索引键匹配查询;大于零表示索引扫描应该停止,因为没有更多可能的匹配。产生该部分匹配查询的操作符的策略号n将被提供,可以通过其语义决定什么时候结束扫描。还有,extra_data是由extractQuery产生的额外数据数组中的对应元素,如果没有则为NULL。空值不会被传递给这个函数。

void options(local_relopts *relopts)

定义一组用户可见的参数以控制操作符类的行为。

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

由于索引值的键提取和键在GIN中的表示都是灵活的,它们可能取决于用户指定的参数。

要支持部分匹配查询,一个操作符类必须提供comparePartial方法,并且它的extractQuery方法必须在遇到一个部分匹配查询时设置pmatch参数。详见第 67.4.2 节

上面提到的多个Datum值的实际数据类型随着操作符类而变化。 被传递给extractValue的项值总是操作符类的输入类型, 并且所有的键值必须是类的STORAGE类型。被传递给extractQueryconsistenttriConsistentquery 参数是由该策略号标识的类成员操作符的右手边输入类型。 这不需要和被索引类型相同,只要正确类型的键值能从其中被抽取出来。不过, 推荐这三个支持函数的 SQL 声明对query参数使用操作符类的被 索引数据类型,即便实际类型可能是某种其他依赖于操作符的东西时也应如此。