GIN 接口具有很高的抽象层次,访问方法实现者只需实现所访问数据类型的语义。 GIN 层本身会处理并发、日志记录以及树结构的搜索。
要让一个 GIN 访问方法工作起来,只需实现少数几个用户定义的方法, 它们定义了树中键的行为,以及键、被索引项和可索引查询之间的关系。简言之, GIN 将可扩展性与通用性、代码重用以及清晰的接口结合在一起。
GIN 操作符类必须提供两个方法:
Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags) #给定一个要建立索引的项,返回一个用 palloc 分配的键数组。返回键的数量必须存入 *nkeys。如果任一键可以为 null,还要另外 palloc 一个包含 *nkeys 个 bool 字段的数组,将其地址存入 *nullFlags,并按需设置这些空值标志。如果所有键都非空, *nullFlags 可以保持为 NULL(其初始值)。 如果该项不包含任何键,则返回值可以为 NULL。
Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode) #给定一个待查询的值,返回一个用 palloc 分配的键数组;也就是说, query 是一个可索引操作符右侧的值,而该操作符左侧是被索引列。 n 是该操作符在操作符类中的策略号(见 Section 36.16.2)。 通常,extractQuery 需要查看 n,以确定 query 的数据类型,以及应采用何种方法提取键值。返回键的数量必须存入 *nkeys。如果任一键可以为 null,还要另外 palloc 一个包含 *nkeys 个 bool 字段的数组,将其地址存入 *nullFlags,并按需设置这些空值标志。如果所有键都非空, *nullFlags 可以保持为 NULL(其初始值)。 如果 query 不包含任何键,则返回值可以为 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 个 bool 的数组,并将其地址存入 *pmatch。若相应键需要部分匹配, 则数组对应元素应设置为 true,否则设置为 false。如果 *pmatch 被设置为 NULL,那么 GIN 认为不需要部分匹配。 该变量在调用前会初始化为 NULL,因此不支持部分匹配的操作符类可以直接忽略此参数。
extra_data 是一个输出参数,用于让 extractQuery 向 consistent 和 comparePartial 方法传递额外数据。 要使用它,extractQuery 必须分配一个包含 *nkeys 个指针的数组,并将其地址存入 *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 的查询操作符,则返回 true; 如果同时返回需要复核的指示,那么 true 也可以只表示它“可能满足”。 该函数无法直接访问被索引项的值,因为 GIN 并不显式存储项。 它所能利用的是这样一种信息:从查询中提取出的哪些键值出现在给定的被索引项中。 check 数组长度为 nkeys,这与先前针对该 query 数据由 extractQuery 返回的键数量相同。 如果被索引项包含相应查询键,则 check 数组中的对应元素为 true; 也就是说,如果 check[i] == true,则 extractQuery 结果数组中的第 i 个键存在于该被索引项中。传入原始 query 数据值, 是为了让 consistent 方法在需要时可以查看它;同样也会传入先前由 extractQuery 返回的 queryKeys[] 和 nullFlags[] 数组。extra_data 则是 extractQuery 返回的额外数据数组,如果没有则为 NULL。
当 extractQuery 在 queryKeys[] 中返回一个 null 键时, 若被索引项包含 null 键,则对应的 check[] 元素为 true;也就是说, check[] 的语义类似于 IS NOT DISTINCT FROM。 如果 consistent 函数需要区分普通值匹配与 null 匹配, 它可以检查对应的 nullFlags[] 元素。
成功时,如果需要根据查询操作符重新检查堆元组,则 *recheck 应设置为 true; 如果索引测试是精确的,则设置为 false。也就是说,返回 false 保证该堆元组不匹配查询; 返回 true 且 *recheck 为 false 保证该堆元组匹配查询; 返回 true 且 *recheck 为 true 则表示该堆元组可能匹配查询, 因此需要取出该元组,并直接对最初被索引的项值重新计算查询操作符以完成复核。
GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[]) #triConsistent 与 consistent 类似,不过 check 向量中的元素不是布尔值,而是每个键都有三种可能的值: GIN_TRUE、GIN_FALSE 和 GIN_MAYBE。 GIN_FALSE 和 GIN_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) #比较两个键(不是被索引项!),并返回一个整数:小于零、等于零或大于零, 分别表示第一个键小于、等于或大于第二个键。null 键绝不会被传递给这个函数。
或者,如果操作符类没有提供 compare 方法,GIN 将查找该索引键数据类型的默认 B-树操作符类,并使用其比较函数。建议在仅面向单一数据类型的 GIN 操作符类中显式指定比较函数,因为查找 B-树操作符类会消耗少量周期。 不过,多态 GIN 操作符类(例如 array_ops)通常无法指定单一比较函数。
用于 GIN 的操作符类还可以选择性提供下列方法:
int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data) #比较部分匹配查询键与索引键。返回一个整数,其符号表示结果:小于零表示索引键不匹配查询, 但索引扫描应继续;零表示索引键匹配查询;大于零表示索引扫描应停止,因为后续不可能再有匹配项。 产生该部分匹配查询的操作符的策略号 n 会被传入,以便在需要其语义来决定何时结束扫描时使用。 另外,extra_data 是由 extractQuery 生成的额外数据数组中的对应元素, 如果没有则为 NULL。null 键绝不会被传递给这个函数。
void options(local_relopts *relopts) #定义一组用户可见的参数,用于控制操作符类行为。
options 函数会接收一个指向 local_relopts 结构的指针,需要用一组特定于操作符类的选项填充它。这些选项可通过 PG_HAS_OPCLASS_OPTIONS() 和 PG_GET_OPCLASS_OPTIONS() 宏,在其他支持函数中访问。
由于被索引值的键提取方式以及键在 GIN 中的表示都很灵活, 它们可能依赖用户指定的参数。
要支持“部分匹配”查询,操作符类必须提供 comparePartial 方法, 并且其 extractQuery 方法在遇到部分匹配查询时必须设置 pmatch 参数。详见 Section 65.4.2。
上文提到的各种 Datum 值,其实际数据类型会因操作符类而异。 传给 extractValue 的项值始终是该操作符类的输入类型,而所有键值都必须是该类的 STORAGE 类型。传给 extractQuery、 consistent 和 triConsistent 的 query 参数类型,是由该策略号标识的类成员操作符右侧输入类型。 只要能够从中提取出正确类型的键值,它就不必与被索引类型相同。不过,建议在这三个支持函数的 SQL 声明中,对 query 参数使用该操作符类的被索引数据类型, 尽管依据具体操作符,实际类型可能是别的类型。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。