70.3. 可扩展性#
GIN接口具有很高的抽象级别,要求访问方法实现者仅实现所访问数据类型的语义。GIN层本身负责并发、日志记录和搜索树结构。
要让GIN访问方法工作,只需实现几个用户定义的方法,这些方法定义了树中键的行为以及键、索引项和可索引查询之间的关系。简而言之,GIN将可扩展性与通用性、代码重用和清晰的界面结合在一起。
对于GIN的运算符类,必须提供两种方法
Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)给定要编制索引的项,返回已分配的键数组。返回的键数必须存储到
*nkeys中。如果任何键可以为空,还要分配一个*nkeysbool字段的数组,将其地址存储在*nullFlags中,并根据需要设置这些空标志。如果所有键都不为空,则可以将*nullFlags保留为NULL(其初始值)。如果该项不包含任何键,则返回值可以为NULL。Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)给定要查询的值,返回已分配的键数组;也就是说,
query是可索引运算符右侧的值,其左侧是索引列。n是运算符类中运算符的策略编号(请参阅 第 38.16.2 节)。通常,extractQuery需要查阅n以确定query的数据类型以及它应使用的方法来提取键值。返回的键数必须存储到*nkeys中。如果任何键可以为空,还要分配一个*nkeysbool字段的数组,将其地址存储在*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必须分配一个包含*nkeysbool的数组,并将它的地址存储在*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。此函数无权直接访问索引项的值,因为 不会显式存储项。相反,可用的信息是关于从查询中提取的哪些键值出现在给定的索引项中。check数组的长度为nkeys,它与先前由extractQuery为此query数据返回的键数相同。如果索引项包含相应的查询键,则check数组的每个元素都为 true,即,如果 (check[i] == true),则extractQuery结果数组的第 i 个键存在于索引项中。如果consistent方法需要查阅,则会传入原始query数据,queryKeys[]和nullFlags[]数组也是如此,它们先前由extractQuery返回。extra_data是由extractQuery返回的额外数据数组,如果没有,则为NULL。当
extractQuery在queryKeys[]中返回 null 键时,如果索引项包含 null 键,则相应的check[]元素为 true;也就是说,check[]的语义类似于IS NOT DISTINCT FROM。如果consistent函数需要区分常规值匹配和 null 匹配,则它可以检查相应的nullFlags[]元素。如果成功,则如果需要根据查询运算符重新检查堆元组,则应将
*recheck设置为 true,如果索引测试是精确的,则应将其设置为 false。也就是说,false 返回值保证堆元组与查询不匹配;将*recheck设置为 false 的 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_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)比较两个键(不是已编入索引的项!)并返回小于零、零或大于零的整数,表示第一个键小于、等于或大于第二个键。空键永远不会传递给此函数。
或者,如果运算符类不提供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()宏从其他支持函数访问这些选项。由于索引值的关键提取和 中键的表示是灵活的,它们可能依赖于用户指定的参数。
为了支持“部分匹配”查询,运算符类必须提供comparePartial方法,并且其extractQuery方法必须在遇到部分匹配查询时设置pmatch参数。有关详细信息,请参见第 70.4.2 节。
上述各种Datum值的实际数据类型因运算符类而异。传递给extractValue的项值始终为运算符类的输入类型,并且所有键值都必须为类的STORAGE类型。传递给extractQuery、consistent和triConsistent的query参数的类型是策略编号标识的类成员运算符的右手输入类型。只要可以从中提取正确类型的键值,它不必与索引类型相同。但是,建议这些三个支持函数的 SQL 声明对query参数使用 opclass 的索引数据类型,即使实际类型可能根据运算符而有所不同。
