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 37.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 就会查找索引键数据类型的默认 btree 操作符类,并使用其比较函数。对于只适用于一种数据类型的 GIN 操作符类,建议指定比较函数,因为查找 btree 操作符类需要耗费一些处理器周期。但是,多态 GIN 操作符类(例如array_ops)通常无法指定单一的比较函数。
作为可选项,GIN操作符类可以提供以下方法:
int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)比较部分匹配查询键与索引键。返回一个整数,其符号表示结果:小于零表示索引键不匹配查询, 但索引扫描应继续;零表示索引键匹配查询;大于零表示索引扫描应停止,因为后续不可能再有匹配项。 产生该部分匹配查询的操作符的策略号 n 会被传入,以便在需要其语义来决定何时结束扫描时使用。 另外,extra_data 是由 extractQuery 生成的额外数据数组中的对应元素, 如果没有则为 NULL。null 键绝不会被传递给这个函数。
要支持“部分匹配”查询,操作符类必须提供 comparePartial 方法, 并且其 extractQuery 方法在遇到部分匹配查询时必须设置 pmatch 参数。详见 Section 66.4.2。
上文提到的各种 Datum 值,其实际数据类型会因操作符类而异。 传给 extractValue 的项值始终是该操作符类的输入类型,而所有键值都必须是该类的 STORAGE 类型。传给 extractQuery、 consistent 和 triConsistent 的 query 参数类型,是由该策略号标识的类成员操作符右侧输入类型。 只要能够从中提取出正确类型的键值,它就不必与被索引类型相同。不过,建议在这三个支持函数的 SQL 声明中,对 query 参数使用该操作符类的被索引数据类型, 尽管依据具体操作符,实际类型可能是别的类型。