Postgresql 中文操作指南

70.3. Extensibility #

GIN 接口具有高度的抽象层,要求访问方法实现者只实现被访问的数据类型的语义。GIN 层本身负责并发、日志记录和搜索树结构。

激活 GIN 访问方法只需要实现几个用户定义的方法,这些方法定义树中键的行为以及键、已编制索引的项和可编制索引的查询之间的关系。简而言之,GIN 将可扩展性和通用性、代码重用和简洁的接口结合在一起。

GIN 的一个操作员类必须提供两种方法:

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

    • 根据要索引的项目返回一个已分配 palloc 的键数组。返回的键的数量必须存储到 *nkeys 中。如果任何键可以为 null,也请分配 palloc 一个 *nkeys bool 字段数组,将其地址存储在 *nullFlags 处,并根据需要设置这些 null 标志。如果所有键都非空,则可以将 *nullFlags 留为 NULL (其初始值)。如果项目不包含任何键,则返回值可以是 NULL

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

    • 根据要查询的值返回一个已分配 palloc 的键数组;也就是说, query 是索引列的左侧是可索引运算符右侧的值。 n 是运算符类中运算符的策略编号(请参阅 Section 38.16.2 )。通常, extractQuery 需要咨询 n ,以确定 query 的数据类型以及它应该用于提取键值的方法。返回的键的数量必须存储到 *nkeys 中。如果任何键可以为 null,也请分配 palloc 一个 *nkeys bool 字段数组,将其地址存储在 *nullFlags 处,并根据需要设置这些 null 标志。如果所有键都非空,则可以将 *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_s and store its address at _*pmatch 数组。如果相应的键需要部分匹配,则数组的每个元素应设为 true;如果不匹配,则设为 false。如果 *pmatch 设置为 NULL,则 GIN 假定不需要部分匹配。该变量在调用前初始化为 NULL,因此不支持部分匹配的运算符类可以忽略此参数。

    • extra_data 是一个输出参数,它允许 extractQuery 将附加数据传递给 consistentcomparePartial 方法。要使用它,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。此函数无法直接访问已索引项的值,因为 GIN 不显式存储项目。相反,可以了解到从查询中提取的哪些键值出现在给定的索引项中。 check 数组的长度为 nkeys ,这与之前 extractQuery 为此 query 数据返回的键数量相同。如果索引项包含相应的查询键,则 check 数组中的每个元素都为 true,即如果 (check[i] == true),则 extractQuery 结果数组的第 i 个键存在于索引项中。原始 query 数据会被传递,以防 consistent 方法需要咨询它,返回 queryKeys[]nullFlags[] 数组也是如此,这些数组之前是由 extractQuery 返回的。 extra_data 是由 extractQuery 返回的额外数据数组,如果没有则为 NULL

    • 当_extractQuery_在_queryKeys[]中返回一个空键时,如果索引项包含一个空键,则相应的_check[]_元素为true;也就是说,_check[]_的语义类似于_IS NOT DISTINCT FROM。如果_consistent_函数需要区分正则值匹配和空匹配,它可以检查相应的_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_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 将查找索引键数据类型的默认 B 树运算符类,并使用其比较函数。建议在仅针对一种数据类型的 GIN 运算符类中指定比较函数,因为查找 B 树运算符类会花费一些周期。然而,多态 GIN 运算符类(例如 array_ops)通常不能指定单个比较函数。

GIN 的运算符类可以根据需要提供以下方法:

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

    • 将部分匹配查询键与索引键进行比较。返回一个整数,其符号表示结果:小于零表示索引键与查询不匹配,但索引扫描应继续;零表示索引键与查询匹配;大于零表示索引扫描应停止,因为不可能再进行匹配。运算符的策略编号 n(生成了部分匹配查询)已提供,以防需要其语义来确定何时结束扫描。此外,extra_dataextractQuery 制作的附加数据数组的相应元素,如果不存在,则为 NULL。空键永远不会传递给这个函数。

  • void options(local_relopts *relopts)

    • 定义一组用户可见的参数来控制运算符类的行为。

    • options 函数会传递一个指向 local_relopts 结构的指针,该结构需要填充一组特定于运算符类的选项。可以使用 PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏从其他支持函数访问这些选项。

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

为了支持“部分匹配”查询,运算符类必须提供_comparePartial_方法,并且其_extractQuery_方法必须在遇到部分匹配查询时设置_pmatch_参数。请参阅 Section 70.4.2以获取详细信息。

上述各种 Datum 值的实际数据类型因操作符类而异。传递到 extractValue 的项值始终为操作符类的输入类型,且所有键值都必须为该类的 STORAGE 类型。传递到 extractQueryconsistenttriConsistentquery 参数的类型是策略编号所标识的类成员操作符的右手输入类型。只要可以从中提取正确类型的键值,它不必与索引类型相同。然而,建议这些三个支持函数的 SQL 声明对 query 参数使用 opclass 的索引数据类型,即使实际类型可能根据操作符而有所不同。