# 66.3.

可扩展性SP-GiST 提供了一个高级抽象的接口,要求访问方法开发人员只实现特定于给定数据类型的方法。SP-GiST 核心负责高效的磁盘映射和树结构搜索。

它还负责并发和日志记录注意事项。SP-GiST 树的叶元组通常包含与索引列具有相同数据类型的值,尽管它们也可能包含索引列的有损表示。存储在根级别的叶元组将直接表示原始索引数据值,但较低级别的叶元组可能仅包含部分值,例如后缀。

在这种情况下,操作符类支持函数必须能够使用从内部元组中积累的信息来重建原始值,这些内部元组通过这些信息到达叶级。当使用创建 SP-GiST 索引时包括列,这些列的值也存储在叶元组中。包括

列与 SP-GiST 运算符类无关,因此这里不再进一步讨论。内部元组更复杂,因为它们是搜索树中的分支点。每个内部元组包含一组一个或多个节点,代表相似叶值的组。一个节点包含一个下行链路,该下行链路要么通向另一个较低级别的内部元组,要么通向一个简短的叶元组列表,这些叶元组都位于同一索引页面上。每个节点通常有一个标签描述它;例如,在基数树中,节点标签可以是字符串值的下一个字符。(或者,一个操作符类可以省略节点标签,如果它与所有内部元组的一组固定节点一起工作;参见第 66.4.2 节.) 可选地,一个内部元组可以有一个字首描述其所有成员的值。在基数树中,这可能是表示字符串的公共前缀。前缀值不一定是真正的前缀,可以是算子类需要的任何数据;例如,在四叉树中,它可以存储测量四个象限的中心点。然后,四叉树内部元组也将包含四个节点,这些节点对应于该中心点周围的象限。

一些树算法需要了解当前元组的级别(或深度),因此 SP-GiST 核心为运算符类提供了在下降树时管理级别计数的可能性。还支持在需要时增量重建表示的值,以及传递额外的数据(称为遍历值) 在树下降期间。

# 笔记

SP-GiST 核心代码负责处理空条目。尽管 SP-GiST 索引确实在索引列中存储空值条目,但这对索引运算符类代码是隐藏的:不会将空索引条目或搜索条件传递给运算符类方法。(假设 SP-GiST 运算符是严格的,因此对于空值不能成功。)因此这里不进一步讨论空值。

SP-GiST 的索引运算符类必须提供五种用户定义的方法,其中两种是可选的。所有五种强制方法都遵循接受两种的约定内部的参数,第一个参数是指向包含支持方法的输入值的 C 结构的指针,而第二个参数是指向必须放置输出值的 C 结构的指针。四个强制方法只返回空白,因为它们的所有结果都出现在输出结构中;但叶一致返回一个布尔值结果。这些方法不得修改其输入结构的任何字段。在所有情况下,输出结构都在调用用户定义的方法之前初始化为零。可选的第六种方法压缩接受一个基准被索引为唯一的参数,并返回一个适合物理存储在叶元组中的值。可选的第七种方法选项接受一个内部的指向 C 结构的指针,其中应该放置 opclass 特定的参数,并返回空白.

五个强制性的用户定义方法是:

配置

返回有关索引实现的静态信息,包括前缀和节点标签数据类型的数据类型 OID。

函数的 SQL 声明必须如下所示:

CREATE FUNCTION my_config(internal, internal) RETURNS void ...

第一个参数是一个指向spgConfigInC 结构,包含函数的输入数据。第二个参数是一个指向spgConfigOutC 结构,函数必须用结果数据填充该结构。

typedef struct spgConfigIn
{
    Oid         attType;        /* Data type to be indexed */
} spgConfigIn;

typedef struct spgConfigOut
{
    Oid         prefixType;     /* Data type of inner-tuple prefixes */
    Oid         labelType;      /* Data type of inner-tuple node labels */
    Oid         leafType;       /* Data type of leaf-tuple values */
    bool        canReturnData;  /* Opclass can reconstruct original data */
    bool        longValuesOK;   /* Opclass can cope with values > 1 page */
} spgConfigOut;

属性类型被传递以支持多态索引运算符类;对于普通的固定数据类型运算符类,它总是具有相同的值,因此可以忽略。

对于不使用前缀的运算符类,前缀类型可以设置为虚空.同样,对于不使用节点标签的运算符类,标签类型可以设置为虚空.可以返回数据如果操作符类能够重建最初提供的索引值,则应该设置为真。长值确定只有当属性类型是可变长度的,并且操作符类能够通过重复后缀来分割长值(参见第 66.4.1 节)。

叶型应该与操作符类定义的索引存储类型相匹配opckeytype目录条目。(注意opckeytype可以为零,表示存储类型与运算符类的输入类型相同,这是最常见的情况。)出于向后兼容的原因,配置方法可以设置叶型到某个其他值,并且将使用该值;但这已被弃用,因为随后在目录中错误地标识了索引内容。另外,允许离开叶型未初始化(零);这被解释为意味着索引存储类型派生自opckeytype.

什么时候属性类型叶型不同,可选方法压缩必须提供。方法压缩负责转换要索引的基准属性类型叶型.

选择

选择一种将新值插入内部元组的方法。

函数的 SQL 声明必须如下所示:

CREATE FUNCTION my_choose(internal, internal) RETURNS void ...

第一个参数是一个指向spgChooseInC 结构,包含函数的输入数据。第二个参数是一个指向spgChooseOutC 结构,函数必须用结果数据填充该结构。

typedef struct spgChooseIn
{
    Datum       datum;          /* original datum to be indexed */
    Datum       leafDatum;      /* current datum to be stored at leaf */
    int         level;          /* current level (counting from zero) */

    /* Data from current inner tuple */
    bool        allTheSame;     /* tuple is marked all-the-same? */
    bool        hasPrefix;      /* tuple has a prefix? */
    Datum       prefixDatum;    /* if so, the prefix value */
    int         nNodes;         /* number of nodes in the inner tuple */
    Datum      *nodeLabels;     /* node label values (NULL if none) */
} spgChooseIn;

typedef enum spgChooseResultType
{
    spgMatchNode = 1,           /* descend into existing node */
    spgAddNode,                 /* add a node to the inner tuple */
    spgSplitTuple               /* split inner tuple (change its prefix) */
} spgChooseResultType;

typedef struct spgChooseOut
{
    spgChooseResultType resultType;     /* action code, see above */
    union
    {
        struct                  /* results for spgMatchNode */
        {
            int         nodeN;      /* descend to this node (index from 0) */
            int         levelAdd;   /* increment level by this much */
            Datum       restDatum;  /* new leaf datum */
        }           matchNode;
        struct                  /* results for spgAddNode */
        {
            Datum       nodeLabel;  /* new node's label */
            int         nodeN;      /* where to insert it (index from 0) */
        }           addNode;
        struct                  /* results for spgSplitTuple */
        {
            /* Info to form new upper-level inner tuple with one child tuple */
            bool        prefixHasPrefix;    /* tuple should have a prefix? */
            Datum       prefixPrefixDatum;  /* if so, its value */
            int         prefixNNodes;       /* number of nodes */
            Datum      *prefixNodeLabels;   /* their labels (or NULL for
                                             * no labels) */
            int         childNodeN;         /* which node gets child tuple */

            /* Info to form new lower-level inner tuple with all old nodes */
            bool        postfixHasPrefix;   /* tuple should have a prefix? */
            Datum       postfixPrefixDatum; /* if so, its value */
        }           splitTuple;
    }           result;
} spgChooseOut;

基准是原始数据spgConfigIn.属性类型要插入索引的类型。叶基准是一个值spgConfigOut.叶型类型,最初是方法的结果压缩应用于基准当方法压缩提供,或相同的值基准除此以外。叶基准可以在树的较低级别更改,如果选择或者分拣方法改变它。当插入搜索到达叶子页时,当前值为叶基准是将存储在新创建的叶元组中的内容。等级是当前内部元组的级别,从根级别的零开始。都一样如果当前内部元组被标记为包含多个等效节点,则为真(参见第 66.4.3 节)。有前缀如果当前内部元组包含前缀,则为真;如果是这样的话,前缀基准是它的价值。节点是内部元组中包含的子节点的数量,并且节点标签是其标签值的数组,如果没有标签,则为NULL。

这个选择函数可以确定新值是否与现有子节点之一匹配,或者必须添加新的子节点,或者新值是否与元组前缀不一致,因此必须拆分内部元组以创建限制较少的前缀。

如果新值与现有子节点之一匹配,则设置结果类型spgMatchNode设置诺登到节点数组中该节点的索引(从零开始)。设置levelAdd增加数量如果运算符类不使用级别,则将其保留为零。设置重新基准相等叶数据如果操作员类没有将基准从一个级别修改到下一个级别,或以其他方式将其设置为要用作叶数据在下一个层面。

如果必须添加新的子节点,请设置结果类型spgAddNode设置诺德拉贝尔要用于新节点的标签,并设置诺登到要在节点数组中插入节点的索引(从零开始)。添加节点后选择函数将使用修改后的内部元组再次调用;这个电话应该会导致spgMatchNode后果

如果新值与元组前缀不一致,请设置结果类型spgSplitTuple。此操作将所有现有节点移动到一个新的较低级别内部元组中,并将现有内部元组替换为一个具有指向新的较低级别内部元组的单个下行链路的元组。设置前缀指示新的上层元组是否应该有前缀,如果设置了前缀预膨胀指向前缀值。此新前缀值的限制性必须比原始值小得多,才能接受要编制索引的新值。设置前缀节点到新元组中所需的节点数,并设置前缀节点标号指向包含标签的palloc数组,或者如果不需要节点标签,则为NULL。请注意,新的上层元组的总大小不得超过它要替换的元组的总大小;这将限制新前缀和新标签的长度。设置childNodeN到将下行链路到新的较低级别内部元组的节点的索引(从零开始)。设置后缀前缀指示新的较低级别内部元组是否应具有前缀,如果设置了前缀后固定指向前缀值。这两个前缀和下行链路节点标签(如果有)的组合必须与原始前缀具有相同的含义,因为没有机会更改移动到新的较低级别元组的节点标签,也没有机会更改任何子索引项。分割节点后选择函数将使用替换的内部元组再次调用。这个电话可能会回电spgAddNode结果,如果spgSplitTuple行动最后选择必须回来spgMatchNode允许插入下降到下一个级别。

皮克斯普利特

决定如何在一组叶元组上创建新的内部元组。

函数的SQL声明必须如下所示:

CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...

第一个参数是指向spgPickSplitInC结构,包含函数的输入数据。第二个参数是指向spgPickSplitOutC结构,函数必须用结果数据填充该结构。

typedef struct spgPickSplitIn
{
    int         nTuples;        /* number of leaf tuples */
    Datum      *datums;         /* their datums (array of length nTuples) */
    int         level;          /* current level (counting from zero) */
} spgPickSplitIn;

typedef struct spgPickSplitOut
{
    bool        hasPrefix;      /* new inner tuple should have a prefix? */
    Datum       prefixDatum;    /* if so, its value */

    int         nNodes;         /* number of nodes for new inner tuple */
    Datum      *nodeLabels;     /* their labels (or NULL for no labels) */

    int        *mapTuplesToNodes;   /* node index for each leaf tuple */
    Datum      *leafTupleDatums;    /* datum to store in each new leaf tuple */
} spgPickSplitOut;

N倍是提供的叶元组数。基准是它们的基准值的数组SPGConfiguut.叶型类型数量是所有叶元组共享的当前级别,它将成为新的内部元组的级别。

设置hasPrefix指示新的内部元组是否应该有前缀,如果设置了前缀前驱体指向前缀值。设置n节点指示新内部元组将包含的节点数,并设置诺德拉贝尔斯设置为其标签值的数组,如果不需要节点标签,则设置为NULL。设置mapTuplesToNodes指向一个数组,该数组给出每个叶元组应分配给的节点的索引(从零开始)。设置叶元宝存储在新叶元组中的值数组(这些值与输入值相同)基准如果操作员类没有将基准从一个级别修改到下一个级别)。请注意皮克斯普利特职能部门负责将诺德拉贝尔斯, mapTuplesToNodes叶元宝数组。

如果提供了多个叶元组,则皮克斯普利特函数将它们分为多个节点;否则,不可能在多个页面上拆分叶元组,这是此操作的最终目的。因此,如果皮克斯普利特函数最终将所有叶元组放在同一个节点中,核心SP GiST代码将覆盖该决定,并生成一个内部元组,其中叶元组随机分配给几个具有相同标签的节点。这样的元组是有标记的照样表明这已经发生。这个选择内部一致函数必须适当注意这样的内部元组。看见第66.4.3节了解更多信息。

皮克斯普利特仅当配置函数集朗瓦卢索克设置为true,并提供了一个大于一页的输入值。在这种情况下,操作的要点是去掉前缀并生成一个新的、较短的叶基准值。将重复调用,直到生成足够短的叶子数据,以适合页面。看见第66.4.1节了解更多信息。

内部一致

返回树搜索期间要遵循的节点集(分支)。

函数的SQL声明必须如下所示:

CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...

第一个参数是指向spgInnerConsistentInC结构,包含函数的输入数据。第二个参数是指向spgInnerConsistentOutC结构,函数必须用结果数据填充该结构。

typedef struct spgInnerConsistentIn
{
    ScanKey     scankeys;       /* array of operators and comparison values */
    ScanKey     orderbys;       /* array of ordering operators and comparison
                                 * values */
    int         nkeys;          /* length of scankeys array */
    int         norderbys;      /* length of orderbys array */

    Datum       reconstructedValue;     /* value reconstructed at parent */
    void       *traversalValue; /* opclass-specific traverse value */
    MemoryContext traversalMemoryContext;   /* put new traverse values here */
    int         level;          /* current level (counting from zero) */
    bool        returnData;     /* original data must be returned? */

    /* Data from current inner tuple */
    bool        allTheSame;     /* tuple is marked all-the-same? */
    bool        hasPrefix;      /* tuple has a prefix? */
    Datum       prefixDatum;    /* if so, the prefix value */
    int         nNodes;         /* number of nodes in the inner tuple */
    Datum      *nodeLabels;     /* node label values (NULL if none) */
} spgInnerConsistentIn;

typedef struct spgInnerConsistentOut
{
    int         nNodes;         /* number of child nodes to be visited */
    int        *nodeNumbers;    /* their indexes in the node array */
    int        *levelAdds;      /* increment level by this much for each */
    Datum      *reconstructedValues;    /* associated reconstructed values */
    void      **traversalValues;        /* opclass-specific traverse values */
    double    **distances;              /* associated distances */
} spgInnerConsistentOut;

阵列扫描键,长度肯基,描述索引搜索条件。这些条件与和结合在一起——只有满足所有条件的索引项才是有趣的。(注意肯基=0表示所有索引项都满足查询。)通常,一致性函数只关心sk_战略sk_论点每个数组项的字段,分别给出可索引运算符和比较值。尤其是不需要检查sk_旗查看比较值是否为空,因为SP GiST核心代码将过滤掉此类条件。阵列订货人,长度诺德比,以相同的方式描述排序运算符(如果有)。重建值是为父元组重构的值;它是(基准)0在根级别,或者如果内部一致函数未在父级提供值。遍历值是指向从上一次调用内部一致在父索引元组上,或在根级别为NULL。traversalMemoryContext存储输出遍历值的内存上下文(见下文)。数量是当前内部元组的级别,根级别从零开始。返回数据符合事实的如果此查询需要重建数据;只有当配置函数断言返回数据. 照样如果当前内部元组标记为“所有相同”,则为true;在这种情况下,所有节点都有相同的标签(如果有的话),因此它们要么全部匹配,要么没有匹配查询(请参阅)第66.4.3节). hasPrefix如果当前内部元组包含前缀,则为true;如果是这样,前驱体这就是它的价值所在。n节点是内部元组中包含的子节点数,以及诺德拉贝尔斯是其标签值的数组,如果节点没有标签,则为NULL。

n节点必须设置为搜索需要访问的子节点数,以及点名者必须设置为它们的索引数组。如果操作员类跟踪级别,则设置levelAdds下降到要访问的每个节点时所需的级别增量数组。(通常所有节点的增量都相同,但不一定如此,所以使用数组。)如果需要重建值,请设置重建值为要访问的每个子节点重建的值数组;否则,请离开重建值为空。重建的值被假定为SPGConfiguut.叶型(然而,由于核心系统除了可能复制它们之外,不会对它们进行任何处理,因此它们拥有相同的功能就足够了。)泰普伦typbyval属性为叶型)如果执行了有序搜索,则设置距离根据订货人数组(首先处理距离最小的节点)。否则将其保留为空。如果需要将额外的带外信息(“遍历值”)传递到树搜索的较低级别,请设置遍历值到一个适当的遍历值数组,每个要访问的子节点对应一个遍历值;否则,请离开遍历值为空。请注意内部一致职能部门负责将点名者, levelAdds, 距离, 重建值遍历值当前内存上下文中的数组。但是,任何由遍历值数组应该在traversalMemoryContext.每个遍历值必须是一个palloc'd块。

叶_一致

如果叶元组满足查询,则返回true。

函数的SQL声明必须如下所示:

CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...

第一个参数是指向spgLeafConsistentInC结构,包含函数的输入数据。第二个参数是指向spgLeafConsistentOutC结构,函数必须用结果数据填充该结构。

typedef struct spgLeafConsistentIn
{
    ScanKey     scankeys;       /* array of operators and comparison values */
    ScanKey     orderbys;       /* array of ordering operators and comparison
                                 * values */
    int         nkeys;          /* length of scankeys array */
    int         norderbys;      /* length of orderbys array */

    Datum       reconstructedValue;     /* value reconstructed at parent */
    void       *traversalValue; /* opclass-specific traverse value */
    int         level;          /* current level (counting from zero) */
    bool        returnData;     /* original data must be returned? */

    Datum       leafDatum;      /* datum in leaf tuple */
} spgLeafConsistentIn;

typedef struct spgLeafConsistentOut
{
    Datum       leafValue;        /* reconstructed original data, if any */
    bool        recheck;          /* set true if operator must be rechecked */
    bool        recheckDistances; /* set true if distances must be rechecked */
    double     *distances;        /* associated distances */
} spgLeafConsistentOut;

阵列扫描键,长度肯基,描述索引搜索条件。这些条件与和结合在一起,只有满足所有条件的索引项才能满足查询。(注意肯基=0表示所有索引项都满足查询。)通常,一致性函数只关心sk_战略sk_论点每个数组项的字段,分别给出可索引运算符和比较值。尤其是不需要检查sk_旗查看比较值是否为空,因为SP GiST核心代码将过滤掉此类条件。阵列订货人,长度诺德比,以相同的方式描述排序运算符。重建值是为父元组重构的值;它是(基准)0在根级别,或者如果内部一致函数未在父级提供值。遍历值是指向从上一次调用内部一致在父索引元组上,或在根级别为NULL。数量是当前叶元组的级别,根级别从零开始。返回数据符合事实的如果此查询需要重建数据;只有当配置函数断言返回数据. 叶数据它的关键价值是什么SPGConfiguut.叶型存储在当前叶元组中。

函数必须返回符合事实的如果叶元组与查询匹配,或者错误的如果不是的话。在符合事实的如果返回数据符合事实的然后叶价值必须设置为(类型)的值spgConfigIn.attType)最初提供此叶元组的索引。而且复查可能设置为符合事实的如果匹配不确定,则必须将运算符重新应用于实际堆元组以验证匹配。如果执行了有序搜索,则设置距离根据订货人大堆否则将其保留为空。如果返回的距离中至少有一个不准确,则设置重新计算距离这是真的。在这种情况下,执行器将在从堆中获取元组后计算确切的距离,并在需要时对元组重新排序。

可选的用户定义方法包括:

基准压缩(基准输入)

将数据项转换为适合在索引的叶元组中进行物理存储的格式。它接受类型为的值spgConfigIn.attType并返回类型为的值SPGConfiguut.叶型.输出值不得包含不符合要求的TOAST指针。

注:该压紧方法仅应用于要存储的值。一致性方法接收查询扫描键未更改,无需使用压紧.

选项

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

函数的SQL声明必须如下所示:

CREATE OR REPLACE FUNCTION my_options(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

函数被传递一个指向本地重新选择struct,它需要填充一组特定于运算符类的选项。可以使用从其他支持功能访问这些选项PG_有_OPCLASS_选项()PG_GET_OPCLASS_OPTIONS()宏。

由于SP GiST中密钥的表示是灵活的,因此它可能取决于用户指定的参数。

所有SP GiST支持方法通常在短期内存上下文中调用;就是,CurrentMemoryContext将在处理每个元组后重置。因此,担心自己失去的一切并不重要。(小标题)配置方法是一个例外:它应该尽量避免内存泄漏。但通常情况下配置方法只需将常量赋给传递的参数结构。)

如果索引列是可折叠的数据类型,则索引排序规则将使用标准PG_GET_COLLATION()机械装置