PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 18 RC1 » Internes » Méthodes d'accÚs natives des index » Index SP-GiST

65.3. Index SP-GiST #

65.3.1. Introduction #

SP-GiST est une abrĂ©viation pour les espaces gĂ©ographiques partitionnĂ©es avec GiST. SP-GiST supporte les arbres de recherche partitionnĂ©s, qui facilitent le dĂ©veloppement d'un grand nombre de structures de donnĂ©es non balancĂ©es diffĂ©rentes, comme les quadtree, les arbres k-d et les arbres de radix. Le principal intĂ©rĂȘt de ces structures et la division rĂ©guliĂšre de l'espace de recherche en partitions de taille Ă©gales. Les recherches qui correspondent bien avec la rĂšgle de partitionnement peuvent ĂȘtre trĂšs rapides.

Ces fameuses structures de donnĂ©es ont Ă©tĂ© initialement conçues pour une exĂ©cution en mĂ©moire. Dans la mĂ©moire principale, elles sont gĂ©nĂ©ralement conçues comme un ensemble de nƓuds allouĂ©s dynamiquement et reliĂ©s entre eux par des pointeurs. Cette organisation ne peut pas ĂȘtre transposĂ©e directement sur disque car ces suites de pointeurs peuvent nĂ©cessiter un nombre d'accĂšs disque trop important. Au contraire, les structures de donnĂ©es adaptĂ©es au disque devraient permettre de charger simultanĂ©ment un grand nombre de donnĂ©es (high fanout) pour minimiser les accĂšs disque. Le challenge proposĂ© par SP-GiST est de faire correspondre les nƓuds des arbres de recherche avec les pages du disque de maniĂšre Ă  ce qu'une recherche ne nĂ©cessite qu'un faible nombre d'accĂšs disque, mĂȘme si il nĂ©cessite de traverser plusieurs nƓuds.

Tout comme GiST, SP-GiST est destiné à permettre le développement de types de données personnalisées, disposant des méthodes d'accÚs appropriées, par un expert du domaine plutÎt que par un expert en base de données.

Une partie des informations fournies ici sont extraites du site web du projet d'indexation SP-GiST de l'université Purdue. L'implémentation de SP-GiST dans PostgreSQL est principalement maintenue par Teodor Sigaev et Oleg Bartunov, plus d'informations sont disponibles sur leur site web.

65.3.2. Classes d'opĂ©rateurs internes #

La distribution de PostgreSQL inclut les classes d'opĂ©rateur SP-GiST indiquĂ©es dans Tableau 65.2.

Tableau 65.2. Classes d'opĂ©rateurs SP-GiST internes

NomOpérateurs indexablesOpérateurs d'ordre
box_ops<< (box,box)<-> (box,point)
&< (box,box)
&> (box,box)
>> (box,box)
<@ (box,box)
@> (box,box)
~= (box,box)
&& (box,box)
<<| (box,box)
&<| (box,box)
|&> (box,box)
|>> (box,box)
inet_ops<< (inet,inet) 
<<= (inet,inet)
>> (inet,inet)
>>= (inet,inet)
= (inet,inet)
<> (inet,inet)
< (inet,inet)
<= (inet,inet)
> (inet,inet)
>= (inet,inet)
&& (inet,inet)
kd_point_ops|>> (point,point)<-> (point,point)
<< (point,point)
>> (point,point)
<<| (point,point)
~= (point,point)
<@ (point,box)
poly_ops<< (polygon,polygon)<-> (polygon,point)
&< (polygon,polygon)
&> (polygon,polygon)
>> (polygon,polygon)
<@ (polygon,polygon)
@> (polygon,polygon)
~= (polygon,polygon)
&& (polygon,polygon)
<<| (polygon,polygon)
&<| (polygon,polygon)
|>> (polygon,polygon)
|&> (polygon,polygon)
quad_point_ops|>> (point,point)<-> (point,point)
<< (point,point)
>> (point,point)
<<| (point,point)
~= (point,point)
<@ (point,box)
range_ops= (anyrange,anyrange) 
&& (anyrange,anyrange)
@> (anyrange,anyelement)
@> (anyrange,anyrange)
<@ (anyrange,anyrange)
<< (anyrange,anyrange)
>> (anyrange,anyrange)
&< (anyrange,anyrange)
&> (anyrange,anyrange)
-|- (anyrange,anyrange)
text_ops= (text,text) 
< (text,text)
<= (text,text)
> (text,text)
>= (text,text)
~<~ (text,text)
~<=~ (text,text)
~>=~ (text,text)
~>~ (text,text)
^@ (text,text)

Sur les deux classes d'opĂ©rateurs pour le type point, quad_point_ops est celui par dĂ©faut. kd_point_ops gĂšre les mĂȘmes opĂ©rateurs mais utilise une structure de donnĂ©es diffĂ©rente pour l'index, structure pouvant offrir de meilleures performances pour certaines utilisations.

Les classes d'opérateurs quad_point_ops, kd_point_ops et poly_ops supportent l'ordre d'opérateur <->, qui active la recherche de type voisin-le-plus-proche (k-NN) sur des ensembles de données composés de point ou polygon.

65.3.3. ExtensibilitĂ© #

SP-GiST offre une interface avec un haut niveau d'abstraction, imposant au dĂ©veloppeur des mĂ©thodes d'accĂšs de n'implĂ©menter que des mĂ©thodes spĂ©cifiques Ă  un type de donnĂ©e spĂ©cifiĂ©. Le cƓur de SP-GiST est responsable de l'efficacitĂ© du stockage sur le disque et de la recherche dans la structure arborescente. Il s'occupe aussi de la concurrence d'accĂšs et des journaux.

Les lignes des feuilles d'un arbre SP-GiST contiennent habituellement des valeurs du mĂȘme type de donnĂ©es que la colonne indexĂ©e, bien qu'il soit possible qu'ils contiennent des reprĂ©sentations Ă  perte de la colonne indexĂ©e. Les enregistrements des feuilles stockĂ©s Ă  la racine reprĂ©senteront directement la valeur originale de la donnĂ©e indexĂ©e, mais les enregistrements des feuilles Ă  des niveaux plus bas pourraient ne contenir qu'une valeur partielle, telle qu'un suffixe. Dans ce cas, les classes d'opĂ©rateurs des fonctions supportĂ©es devront ĂȘtre capables de reconstruire la valeur originale en utilisant les informations accumulĂ©es dans les lignes intermĂ©diaires au travers du parcours de l'arbre et vers le niveau le plus bas.

Quand un index SP-GiST est créé avec des colonnes INCLUDE, les valeurs de ces colonnes sont aussi stockées dans des enregistrements feuilles. Les colonnes INCLUDE ne concernent pas la classe d'opérateurs SP-GiST, donc elles ne seront pas discutées avec plus de détails ici.

Les lignes internes sont plus complexes car elles relient des points dans l'arbre de recherche. Chaque ligne intermĂ©diaire contient un ensemble d'au moins un nƓud, qui reprĂ©sente des groupes de valeurs similaires de feuilles. Un nƓud contient un lien qui mĂšne vers un autre nƓud de niveau infĂ©rieur, ou une petite liste de lignes de feuilles qui appartiennent toutes Ă  la mĂȘme page d'index. Chaque nƓud a un label qui le dĂ©crit. Par exemple, dans un arbre radix, le label du nƓud peut ĂȘtre le caractĂšre suivant de la chaĂźne de caractĂšre. (Sinon, une classe d'opĂ©rateurs peut omettre les labels des nƓuds si elle fonctionne avec un ensemble fixe de nƓuds pour les enregistrements internes ; voir Section 65.3.4.2.) En option, une ligne intermĂ©diaire peut avoir une valeur de prĂ©fixe qui dĂ©crit tous ses membres. Dans un arbre radix, cela peut ĂȘtre le prĂ©fixe commun des chaĂźnes reprĂ©sentant les donnĂ©es. La valeur du prĂ©fixe n'est pas nĂ©cessairement rĂ©ellement un prĂ©fixe, mais peut ĂȘtre toute donnĂ©e utilisĂ©e par la classe d'opĂ©rateurs. Par exemple, pour un quadtree, il peut stocker le barycentre des quatre points reprĂ©sentĂ© par chaque feuille. Une ligne intermĂ©diaire d'un quadtree contiendra aussi quatre nƓuds correspondants Ă  des points autour de ce point central.

Quelques algorithmes de recherche arborescente nĂ©cessite la connaissance du niveau (ou profondeur) de la ligne en cours, et ainsi le cƓur de SP-GiST fournit aux classes d'opĂ©rateurs la possibilitĂ© de gĂ©rer le dĂ©compte des niveaux lors du parcours de l'arbre. Il fournit aussi le moyen de reconstruire de façon incrĂ©mentale la valeur reprĂ©sentĂ©e lorsque cela est nĂ©cessaire, et pour passer des donnĂ©es supplĂ©mentaires (appelĂ©es valeurs traverses) lors de la descente de l'arbre.

Note

Le code du cƓur de SP-GiST tient aussi compte des valeurs NULL. Bien que les index SP-GiST stockent des entrĂ©es pour les valeurs NULL dans les colonnes indexĂ©es, cette implĂ©mentation reste non apparente au code de l'index de classe d'opĂ©rateur : aucune valeur NULL d'index ou de condition de recherche ne sera jamais transmis aux mĂ©thodes de la classe d'opĂ©rateurs (il est convenu que les opĂ©rateurs SP-GiST sont stricts et ainsi ne peuvent trouver des valeurs NULL). Le cas des valeurs NULL n'est ainsi plus abordĂ© dans les paragraphes qui suivent.

Un index de classe d'opĂ©rateurs pour SP-GiST peut proposer cinq mĂ©thodes personnalisĂ©es, et deux optionnelles. Chacune de ces cinq mĂ©thodes obligatoires doit suivre la convention qui consiste Ă  accepter deux arguments de type internal, le premier Ă©tant un pointeur vers une structure C contenant les valeurs en entrĂ©e de cette mĂ©thode, et le second Ă©tant un pointeur vers une structure C oĂč les valeurs en sortie seront placĂ©es. Quatre de ces mĂ©thodes retournent void car leurs rĂ©sultats sont prĂ©sents dans la structure en sortie. Mais la mĂ©thode leaf_consistent retourne une valeur de type boolean. Les mĂ©thodes ne doivent modifier aucun des champs de la structure en entrĂ©e. Dans tous les cas, la structure en sortie est initialisĂ©e avec des zĂ©ros avant l'appel Ă  la mĂ©thode personnalisĂ©e. La sixiĂšme mĂ©thode, optionnelle, compress accepte un datum Ă  indexer comme seul argument et renvoie une valeur convenable pour un enregistrement physique dans une ligne feuille. La septiĂšme mĂ©thode, optionnelle, est appelĂ©e options et accepte un pointeur internal vers une structure C, remplie avec les paramĂštres spĂ©cifiques Ă  la classe d'opĂ©rateurs, et renvoie void.

Les cinq mĂ©thodes personnalisĂ©es sont :

config

Retourne des informations statiques concernant l'implĂ©mentation des index, incluant les OID du type de donnĂ©es du prĂ©fixe et le type de donnĂ©es du label du nƓud.

La dĂ©claration SQL de la fonction doit ressembler Ă  :

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

Le premier argument est un pointeur vers une structure C spgConfigIn, qui contient les données en entrée de la fonction. Le second argument est un pointeur vers une structure C spgConfigOut, qui permet à la fonction d'y spécifier les données en sortie.

typedef struct spgConfigIn
{
    Oid         attType;        /* Le type de donnée à indexer */
} spgConfigIn;

typedef struct spgConfigOut
{
    Oid         prefixType;     /* Le type de donnée des préfixe des tuples intermédiaires */
    Oid         labelType;      /* Le type de donnĂ©e des labels de nƓud des tuples intermĂ©diaires */
    Oid         leafType;       /* Type de données pour les valeurs de tuple feuille */
    bool        canReturnData;  /* Opclass peut reconstruire les données originales */
    bool        longValuesOK;   /* Opclass sait gérer les valeurs plus grandes qu'une page */
} spgConfigOut;
      

attType est fourni pour gĂ©rer les index polymorphiques de classe d'opĂ©rateurs. Pour les types de donnĂ©es ordinaires de classe d'opĂ©rateurs (fixĂ©s), il aura toujours la mĂȘme valeur et peut ainsi ĂȘtre ignorĂ©.

Pour les classes d'opĂ©rateurs qui n'utilisent pas de prĂ©fixe, prefixType peut ĂȘtre dĂ©fini Ă  VOIDOID. De la mĂȘme façon, pour les classes d'opĂ©rateurs qui n'utilisent pas de label de nƓud, labelType peut ĂȘtre dĂ©fini Ă  VOIDOID. canReturnData peut ĂȘtre dĂ©fini Ă  true si la classe d'opĂ©rateurs est capable de reconstruire la valeur d'index fournie initialement. longValuesOK doit ĂȘtre dĂ©fini Ă  true uniquement lorsque attType est de longueur variable et que la classe d'opĂ©rateur est capable de segmenter les grandes valeurs en rĂ©pĂ©tant les suffixes (voir Section 65.3.4.1).

leafType doit correspondre au type de stockage de l'index dĂ©fini par l'entrĂ©e de catalogue opckeytype de la classe d'opĂ©rateurs. (Notez que opckeytype peut valoir zĂ©ro, impliquant que le type de stockage est le mĂȘme que le type en entrĂ©e de la classe d'opĂ©rateurs, ce qui est la situation la plus commune.) Pour des raisons de compatibilitĂ© ascendante, la mĂ©thode config peut configurer leafType Ă  toute autre valeur, et cette valeur sera utilisĂ©e ; mais ceci est abandonnĂ© car le contenu de l'index est alors incorrectement identifiĂ© dans les catalogues. De plus, il est autorisĂ© de laisser leave leafType non initialisĂ© (zĂ©ro) ; ceci est interprĂ©tĂ© comme signifiant que le type de stockage de l'index est dĂ©rivĂ© de opckeytype.

Quand attType et leafType sont diffĂ©rents, alors la mĂ©thode optionnelle compress doit ĂȘtre fournie. La mĂ©thode compress est responsable de la transformation des datums pour les indexer de attType vers leafType.

choose

Choisit une méthode pour insérer une nouvelle valeur dans une ligne intermédiaire.

La dĂ©claration SQL de la fonction doit ressembler Ă  :

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

Le premier argument est un pointeur vers une structure C spgChooseIn, qui contient les données en entrée de la fonction. Le second argument est un pointeur vers une structure C spgChooseOut, qui permet à la fonction d'y spécifier les données en sortie.

typedef struct spgChooseIn
{
    Datum       datum;          /* donnée initiale à indexer */
    Datum       leafDatum;      /* donnée en cours à stocker dans la feuille */
    int         level;          /* niveau en cours (Ă  partir de 0) */

    /* Données issues de la ligne intermédiaire */
    bool        allTheSame;     /* la ligne contient des valeurs équivalentes ? */
    bool        hasPrefix;      /* la ligne a-t-elle un préfixe? */
    Datum       prefixDatum;    /* si c'est le cas, la valeur de ce préfixe */
    int         nNodes;         /* nombre de nƓuds dans la ligne intermĂ©diaire */
    Datum      *nodeLabels;     /* valeurs du label du nƓud (NULL sinon) */
} spgChooseIn;

typedef enum spgChooseResultType
{
    spgMatchNode = 1,           /* descend dans le nƓud existant */
    spgAddNode,                 /* ajoute un nƓud dans la ligne intermĂ©diaire */
    spgSplitTuple               /* scinde une ligne intermédiaire (modifie son préfixe) */
} spgChooseResultType;

typedef struct spgChooseOut
{
    spgChooseResultType resultType;     /* code d'action, voir plus bas */
    union
    {
        struct                  /* resultats de spgMatchNode */
        {
            int         nodeN;      /* descend dans ce nƓud (à partir de 0) */
            int         levelAdd;   /* incrémente le niveau de cette valeur */
            Datum       restDatum;  /* nouvelle valeur de la feuille */
        }           matchNode;
        struct                  /* résultats de spgAddNode */
        {
            Datum       nodeLabel;  /* nouveau label du nƓud */
            int         nodeN;      /* lĂ  oĂč l'insĂ©rer (Ă  partir de 0) */
        }           addNode;
        struct                  /* résultats pour spgSplitTuple */
        {
	    /* Information pour former une ligne de niveau supĂ©rieur avec un nƓud fils */
            bool        prefixHasPrefix;    /* la ligne doit-elle avoir un préfixe ? */
            Datum       prefixPrefixDatum;  /* si oui, sa valeur */
            int         prefixNNodes;       /* nombre de nƓuds */
            Datum      *prefixNodeLabels;   /* leurs labels (ou NULL si
                                             * aucun label) */
            int         childNodeN;         /* quel nƓud a un nƓud fils */

            /* Informations pour former une nouvelle ligne intermédaire de niveau inférieur
			à partir de tous les anciens nƓuds */
            bool        postfixHasPrefix;   /* la ligne doit-elle avoir un préfixe ? */
            Datum       postfixPrefixDatum; /* si oui, sa valeur */
        }           splitTuple;
    }           result;
} spgChooseOut;
      

datum est la valeur initiale de type spgConfigIn.attType de la donnĂ©e qui a Ă©tĂ© insĂ©rĂ©e dans l'index. leafDatum est une valeur de type spgConfigOut.leafType qui est initialement un rĂ©sultat de la mĂ©thode compress appliquĂ©e Ă  datum quand la mĂ©thode compress est fournie, ou la mĂȘme valeur que datum dans le cas contraire. leafDatum peut changer Ă  des niveaux infĂ©rieurs de l'arbre si la fonction choose ou picksplit change cette valeur. Lorsque la recherche liĂ©e Ă  l'insertion atteint une feuille, la valeur actuelle de leafDatum sera stockĂ©e dans la nouvelle ligne de feuille créée. level est le niveau actuel de la ligne intermĂ©diaire, en considĂ©rant que 0 est le niveau racine. allTheSame est true si la ligne intermĂ©diaire actuelle est marquĂ©e comme contenant plusieurs nƓuds Ă©quivalents. (voir Section 65.3.4.3). hasPrefix est vrai si la ligne intermĂ©diaire actuelle contient un prĂ©fixe ; si c'est le cas, prefixDatum est sa valeur. nNodes est le nombre de nƓuds enfants contenus dans la ligne intermĂ©diaire, et nodeLabels est un tableau des valeurs de leurs labels, ou NULL s'il n'y a pas de labels.

La fonction choose peut dĂ©terminer si la nouvelle valeur correspond Ă  un des nƓuds enfants existants, ou si un nouvel enfant doit ĂȘtre ajoutĂ©, ou si la nouvelle valeur n'est pas consistante avec les prĂ©fixes de ligne et qu'ainsi la ligne intermĂ©diaire doit ĂȘtre dĂ©coupĂ©e pour crĂ©er un prĂ©fixe moins restrictif.

Si la nouvelle valeur correspond Ă  un des nƓuds enfants existants, dĂ©finir resultType Ă  spgMatchNode. et dĂ©finir nodeN Ă  l'index (Ă  partir de 0) du nƓud dans le tableau de nƓud. DĂ©finir levelAdd Ă  l'incrĂ©ment de level nĂ©cessaire pour descendre au travers de ce nƓud, ou le laisser Ă  0 si la classe d'opĂ©rateurs n'utilise pas de niveaux. DĂ©finir restDatum Ă  la valeur de leafDatum si la classe d'opĂ©rateurs ne modifie pas les valeurs d'un niveau au suivant, ou dans le cas contraire, dĂ©finir la valeur modifiĂ©e pour ĂȘtre utilisĂ©e comme valeur de leafDatum au niveau suivant.

Si un nouveau nƓud enfant doit ĂȘtre ajoutĂ©, dĂ©finir resultType Ă  spgAddNode. DĂ©finir nodeLabel au label Ă  utiliser pour le nouveau nƓud, et dĂ©finir nodeN Ă  l'index (de 0) auquel insĂ©rer le nƓud dans le tableau de nƓud. AprĂšs que ce nƓud a Ă©tĂ© ajoutĂ©, la fonction choose sera appelĂ©e Ă  nouveau avec la ligne intermĂ©diaire modifiĂ©e. Cet appel devrait produire un rĂ©sultat spgMatchNode.

Si la nouvelle valeur est cohĂ©rente avec le prĂ©fixe de ligne, dĂ©finir resultType Ă  spgSplitTuple. Cette action dĂ©place tous les nƓuds existants dans le nouveau niveau infĂ©rieur de la ligne intermĂ©diaire, et remplace la ligne intermĂ©diaire existant avec une ligne qui dispose d'un unique nƓud qui est liĂ© Ă  la nouvelle ligne intermĂ©diaire de niveau infĂ©rieur. DĂ©finir prefixHasPrefix pour indiquer si les nouvelles lignes supĂ©rieures doivent avoir un prĂ©fixe, et si c'est le cas, dĂ©finir prefixPrefixDatum Ă  la valeur du prĂ©fixe. Cette nouvelle valeur de prĂ©fixe doit ĂȘtre suffisamment moins restrictive que l'original pour accepter que la nouvelle valeur soit indexĂ©e. DĂ©finir prefixNNodes au nombre de nƓuds nĂ©cessaires pour la nouvelle ligne et dĂ©finir prefixNodeLabels Ă  un tableau allouĂ© avec palloc de leurs labels, ou Ă  NULL si les labels des nƓuds ne sont pas nĂ©cessaires. Noter que la taille totale de la nouvelle ligne supĂ©rieure ne doit pas dĂ©passer la taille totale de la ligne qu'elle remplace ; cela contraint les longueurs des nouveaux prĂ©fixes et labels. DĂ©finir postfixHasPrefix pour indiquer si la nouvelle ligne intermĂ©diaire de niveau infĂ©rieur aura un prĂ©fixe, et dans ce cas dĂ©finir postfixPrefixDatum Ă  la valeur du prĂ©fixe. La combinaison de ces deux prĂ©fixes et le label additionnel doit avoir la mĂȘme signification que le prĂ©fixe original car il n'y a pas de moyen de modifier le label du nƓud qui est dĂ©placĂ© vers la nouvelle ligne de niveau infĂ©rieur, ni de modifier une quelconque entrĂ©e d'index enfant. AprĂšs que ce nƓud ait Ă©tĂ© dĂ©coupĂ©, la fonction choose sera appelĂ©e Ă  nouveau avec la ligne intermĂ©diaire de remplacement. Cet appel devrait retourner un spgAddNode car, Ă  priori, le label du nƓud ajoutĂ© lors de l'Ă©tape de dĂ©coupage ne correspondra pas Ă  la nouvelle valeur. Ainsi, aprĂšs cette Ă©tape, il y aura une troisiĂšme Ă©tape qui retournera finalement spgMatchNode et permettra l'insertion pour descendre au niveau feuille.

picksplit

Décide de la maniÚre à suivre pour créer une ligne intermédiaire à partir d'un ensemble de lignes de feuilles.

La dĂ©claration de fonction SQL doit ressembler Ă  :

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

Le premier argument est un pointeur vers une structure C spgPickSplitIn, qui contient les données en entrée de la fonction. Le second argument est un pointeur vers une structure C spgPickSplitOut, qui permet à la fonction d'y spécifier les données en sortie.

typedef struct spgPickSplitIn
{
    int         nTuples;        /* nombre de lignes feuilles */
    Datum      *datums;         /* leur données (tableau de taille nTuples) */
    int         level;          /* niveau actuel (Ă  partir de 0) */
} spgPickSplitIn;

typedef struct spgPickSplitOut
{
    bool        hasPrefix;      /* les nouvelles lignes intermédiaires doivent-elles avoir un préfixe ? */
    Datum       prefixDatum;    /* si oui, la valeur du préfixe */

    int         nNodes;         /* nombre de nƓud pour une nouvelle ligne intermĂ©diaire */
    Datum      *nodeLabels;     /* leurs labels (ou NULL s'il n'y a aucun label) */

    int        *mapTuplesToNodes;   /* index du nƓud de chaque ligne feuille */
    Datum      *leafTupleDatums;    /* données à stocker dans chaque nouvelle ligne feuille */
} spgPickSplitOut;
      

nTuples est le nombre de lignes feuilles fournies. datums est un tableau de leurs données de type spgConfigOut.leafType. level est le niveau actuel que les lignes feuille concernées partagent, qui deviendra le niveau de la nouvelle ligne intermédiaire.

DĂ©finir hasPrefix pour indiquer que la nouvelle ligne intermĂ©diaire doit avoir un prĂ©fixe, et dans ce cas, dĂ©finir prefixDatum Ă  la valeur de ce prĂ©fixe. DĂ©finir nNodes pour indiquer le nombre de nƓuds que contiendra la nouvelle ligne intermĂ©diaire, et spĂ©cifier dans nodeLabels un tableau de leurs labels, ou NULL si les labels ne sont pas nĂ©cessaires. Attribuer Ă  mapTuplesToNodes un tableau des index (Ă  partir de zĂ©ro) des nƓuds auxquels seront assignĂ©s chaque ligne feuille. Attribuer Ă  leafTupleDatums un tableau des valeurs Ă  stocker dans la nouvelle ligne de feuilles (ces valeurs seront les mĂȘmes que celles des donnĂ©es datums fournies en paramĂštre si la classe d'opĂ©rateurs ne modifie pas les donnĂ©es d'un niveau Ă  un autre). À noter que la fonction picksplit est responsable de l'allocation de mĂ©moire des tableaux nodeLabels, mapTuplesToNodes et leafTupleDatums.

Si plus d'une ligne de feuille est fournie, il est nĂ©cessaire que la fonction picksplit les classent en plus d'un nƓud. Dans le cas contraire, il ne sera pas possible de rĂ©partir les lignes des feuilles sur des pages diffĂ©rentes, ce qui est pourtant l'objectif de cette opĂ©ration. À cet effet, si la fonction picksplit se termine aprĂšs avoir rĂ©parti toutes les lignes des feuilles dans le mĂȘme nƓud, le code du moteur de SP-GiST ne tiendra pas compte de cette dĂ©cision, et gĂ©nĂ©rera une ligne intermĂ©diaire dans lequel chaque ligne de feuille sera assignĂ© alĂ©atoirement Ă  plusieurs nƓuds de labels identiques. De telles lignes sont marquĂ©es allTheSame pour garder une trace de cette dĂ©cision. Les fonctions choose et inner_consistent doivent tenir compte de ces lignes intermĂ©diaires. Voir Section 65.3.4.3 pour plus d'informations.

picksplit peut ĂȘtre appliquĂ© Ă  une unique ligne de feuille lorsque la fonction config dĂ©finit longValuesOK Ă  true et qu'une valeur plus large qu'une page est donnĂ©e en paramĂštre. Dans ce cas, l'objectif de la fonction est d'extraire un prĂ©fixe et de produire une donnĂ©e de feuille moins longue. Cet appel sera rĂ©pĂ©tĂ© jusqu'Ă  ce que la donnĂ©e de la feuille soit suffisamment petite pour tenir dans une page. Voir Section 65.3.4.1 pour plus d'information.

inner_consistent

Retourne un ensemble de nƓuds (branches) à suivre durant une recherche arborescente.

La dĂ©claration SQL de cette fonction doit ressembler Ă  :

CREATE FUNCTION ma_suite_de_nƓuds(internal, internal) RETURNS void ...
      

Le premier argument est un pointeur vers une structure C spgInnerConsistentIn, qui contient les données en entrée de la fonction. Le second argument est un pointeur vers une structure C spgInnerConsistentOut, qui permet à la fonction d'y spécifier les données en sortie.

typedef struct spgInnerConsistentIn
{
    ScanKey     scankeys;       /* tableau d'opérateurs et de valeurs de comparaison */
    ScanKey     orderbys;       /* tableau d'opérateurs de tri et comparaison */
                                 * de valeur */
    int         nkeys;          /* taille du tableau scankeys */
    int         norderbys;      /* taille du tableau orderbys */

    Datum       reconstructedValue;     /* valeur reconstruite au niveau parent */
    MemoryContext traversalMemoryContext;   /* placer les nouvelles valeurs ici */
    int         level;          /* niveau actuel (à partir de zéro) */
    bool        returnData;     /* retourner la valeur originale ? */

    /* Données du tuple intermédiaire en cours */
    bool        allTheSame;     /* la ligne est-elle identifiée comme all-the-same ? */
    bool        hasPrefix;      /* la ligne a-t-elle un préfixe ? */
    Datum       prefixDatum;    /* dans ce cas, la valeur du préfixe */
    int         nNodes;         /* nombre de nƓuds dans la ligne intermĂ©diaire */
    Datum      *nodeLabels;     /* labels du nƓud (NULL si pas de labels) */
    void      **traversalValues;        /* valeurs traverses spécifiques de la classe d'opérateurs */
    double    **distances;              /* distances associées */
} spgInnerConsistentIn;

typedef struct spgInnerConsistentOut
{
    int         nNodes;         /* nombre de nƓuds enfants à visiter */
    int        *nodeNumbers;    /* leurs index dans le tableau de nƓuds */
    int        *levelAdds;      /* l'incrément à apporter au niveau pour chaque enfant */
    Datum      *reconstructedValues;    /* valeurs reconstruites associées */
} spgInnerConsistentOut;
      

Le tableau scankeys, de longueur nkeys, dĂ©crit les conditions de recherche d'index. Ces conditions sont combinĂ©es avec un opĂ©rateur et seules les entrĂ©es d'index qui correspondent Ă  toutes ces conditions sont conservĂ©es (Ă  noter que nkeys = 0 implique que toutes les entrĂ©es d'index sont conservĂ©es). GĂ©nĂ©ralement, la fonction inner_consistent ne tient compte que des champs sk_strategy et sk_argument de chaque entrĂ©e de tableau, qui fournissent respectivement l'opĂ©rateur indexĂ© et la valeur de comparaison. En particulier, il n'est pas nĂ©cessaire de vĂ©rifier si sk_flags est NULL car le moteur de SP-GiST aura complĂ©tĂ© cette valeur. Le tableau orderbys, de longueur norderbys, dĂ©crit les opĂ©rateurs de tri (s'il y en a) de la mĂȘme maniĂšre. reconstructedValue est la valeur reconstruite pour la ligne parent. La valeur est (Datum) 0 au niveau le plus haut ou si la fonction inner_consistent ne fournit pas de valeur pour le niveau supĂ©rieur. traversalValue est un pointer vers toute donnĂ©e traverse passĂ©e Ă  l'appel prĂ©cĂ©dent de inner_consistent sur l'enregistrement parent de l'index, ou NULL Ă  la racine. traversalMemoryContext est le contexte mĂ©moire de stockage des valeurs traverses en sortie (voir ci-dessous). level est le niveau actuel de la ligne intermĂ©diaire, en commençant Ă  0 pour le niveau racine. returnData est true pour la valeur reconstruite pour cette requĂȘte. Ce n'est le cas que si la fonction config dĂ©finit canReturnData. allTheSame est true si la ligne intermĂ©diaire en cours est marquĂ©e « all-the-same Â». Dans ce cas, tous les nƓuds ont le mĂȘme label (si un label est dĂ©fini) et ainsi soit ils correspondent tous Ă  la requĂȘte, soit aucun ne correspond (voir Section 65.3.4.3). hasPrefix est true si la ligne intermĂ©diaire en cours contient un prĂ©fixe. Dans ce cas, prefixDatum est sa valeur. nNodes est le nombre de nƓuds enfants de la ligne intermĂ©diaire, et nodeLabels est un tableau de leurs labels, ou NULL si les nƓuds n'ont pas de labels.

nNodes doit ĂȘtre dĂ©fini comme le nombre de nƓuds enfants qui doivent ĂȘtre visitĂ©s durant la recherche, et nodeNumbers doit ĂȘtre dĂ©fini comme le tableau de leurs index. Si la classe d'opĂ©rateurs effectue le suivi des niveaux, dĂ©finir levelAdds comme un tableau des incrĂ©ments Ă  ajouter aux niveaux pour descendre vers chaque nƓud Ă  visiter (dans la plupart des cas, les incrĂ©ments seront les mĂȘmes pour chaque nƓud, mais ce n'est pas systĂ©matique, et ainsi un tableau est employĂ©). Si la reconstruction de la valeur est nĂ©cessaire, dĂ©finir reconstructedValues en un tableau des valeurs reconstruites pour chaque nƓud enfant Ă  visiter. Sinon, laisser reconstructedValues Ă  la valeur NULL. Si une recherche triĂ©e est exĂ©cutĂ©e, initialise distances Ă  un tableau de valeurs de distance suivant le tableau orderbys (les nƓuds avec les plus petites distances seront traitĂ©es en premier). Laisse NULL sinon. Les valeurs reconstruites sont supposĂ©es ĂȘtre de type spgConfigOut.leafType. (NĂ©anmoins, comme le coeur du systĂšme ne fera rien avec elles sauf potentiellementles copier, il est suffisant qu'elles aient les mĂȘmes propriĂ©tĂ©s typlen et typbyval que leafType.) S'il est souhaitable de passer les informations supplĂ©mentaires hors bande (« valeurs traverses Â») pour diminuer les niveaux de l'arbre de recherche, initialiser traversalValues en un tableau des valeurs traverses appropriĂ©es, un pour chaque nƓud enfants Ă  visiter ; sinon laisser traversalValues Ă  NULL. Notez que la fonction inner_consistent est responsable de l'allocation mĂ©moire des tableaux nodeNumbers, levelAdds, distances, reconstructedValues et traversalValues dans le contexte mĂ©moire actuel. NĂ©anmoins, toute valeur traverse en sortie pointĂ©e par le tableau traversalValues devrait ĂȘtre allouĂ©e dans traversalMemoryContext. Chaque valeur traverse doit ĂȘtre un morceau simple allouĂ© avec la fonction palloc.

leaf_consistent

Retourne true si une ligne de feuille satisfait une requĂȘte.

La dĂ©claration SQL de cette fonction doit ressembler Ă  :

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

Le premier argument est un pointeur vers une structure C spgLeafConsistentIn, qui contient les données en entrée de la fonction. Le second argument est un pointeur vers une structure C spgLeafConsistentOut, qui permet à la fonction d'y spécifier les données en sortie.

typedef struct spgLeafConsistentIn
{
    ScanKey     scankeys;       /* tableau d'opérateurs et de valeurs de comparaison */
    ScanKey     orderbys;       /* tableau d'opérateurs de tri et comparaison */
                                 * de valeurs */
    int         nkeys;          /* taille du tableau scankeys */
    int         norderbys;      /* taille du tableau orderbys */

    Datum       reconstructedValue;     /* valeur reconstruite au parent */
    void       *traversalValue; /* valeur traverse spécifique à la classe d'opérateurs */
    int         level;          /* niveau actuel (à partir de zéro) */
    bool        returnData;     /* les donnĂ©es originales doivent-elles ĂȘtre reconstruites ? */

    Datum       leafDatum;      /* données de la ligne de feuille */
} spgLeafConsistentIn;

typedef struct spgLeafConsistentOut
{
    Datum       leafValue;      /* données originales reconstruites, le cas échéant */
    bool        recheck;        /* dĂ©finir Ă  true si l'opĂ©rateur doit ĂȘtre revĂ©rifiĂ© */
    Datum       leafValue;        /* valeur d'origine reconstruite, le cas échéant */
    bool        recheck;          /* positionnĂ© Ă  true si l'opĂ©rateur doit ĂȘtre revĂ©rifiĂ© */
    bool        recheckDistances; /* positionnĂ© Ă  true si les distances doivent ĂȘtre revĂ©rifiĂ©es */
    double     *distances;        /* associated distances */
} spgLeafConsistentOut;
      

Le tableau scankeys, de longueur nkeys, dĂ©crit les conditions de recherche dans l'index. Ces conditions sont uniquement combinĂ©es avec AND -- Seules les entrĂ©es d'index qui satisfont toutes les conditions satisfont la requĂȘte (Notez que nkeys = 0 implique que toutes les entrĂ©es de l'index satisfont la requĂȘte). GĂ©nĂ©ralement, la fonction de recherche ne tient compte que des champs sk_strategy et sk_argument de chaque entrĂ©e du tableau, qui correspondent respectivement Ă  l'opĂ©rateur indexable et Ă  la valeur de comparaison. En particulier, il n'est pas nĂ©cessaire de vĂ©rifier sk_flags pour savoir que la valeur de comparaison est NULL car le code du cƓur de SP-GiST filtre ces conditions. Le tableau orderbys, de taille norderbys, dĂ©crit les opĂ©rateurs de tri de la mĂȘme maniĂšre. reconstructedValue est la valeur reconstruite pour la ligne parent ; Il s'agit de (Datum) 0 au niveau racine ou si la fonction inner_consistent ne fournit pas de valeur au niveau parent. traversalValue est un pointeur vers toute donnĂ©e traverse passĂ©e lors de l'appel prĂ©cĂ©dent Ă  inner_consistent de l'enregistrement parent de l'index ou NULL Ă  la racine. level est le niveau actuel de la ligne de feuille, qui commence Ă  zĂ©ro pour le niveau racine. returnData est true s'il est nĂ©cessaire de reconstruire les donnĂ©es pour cette requĂȘte. Cela ne sera le cas que lorsque la fonction config vĂ©rifie canReturnData. leafDatum est la valeur de la clĂ© stockĂ©e de spgConfigOut.leafType dans la ligne de feuille en cours.

La fonction doit retourner true si la ligne de feuille correspond Ă  la requĂȘte ou false sinon. Dans le cas oĂč la valeur serait true, et que returnData est true alors leafValue doit ĂȘtre dĂ©fini Ă  la valeur originale, de type spgConfigIn.attType fournie pour ĂȘtre indexĂ©e pour cette ligne de feuille. recheck peut ĂȘtre dĂ©fini Ă  true si la correspondance est incertaine et ainsi l'opĂ©rateur doit ĂȘtre rĂ©appliquĂ© Ă  la pile de ligne courante pour vĂ©rifier la correspondance. Si une recherche triĂ©e est effectuĂ©e, positionne distances Ă  un tableau de distance suivant le tableau orderbys Laisse NULL sinon. Si au moins une des distances retournĂ©es n'est pas exacte, positionne recheckDistances Ă  true. Dans ce cas, l'exĂ©cuteur recalculera la distance exacte aprĂšs avoir rĂ©cupĂ©rĂ© toutes les lignes de la table, et rĂ©ordonnera les lignes si besoin.

Les mĂ©thodes optionnelles dĂ©finies par l'utilisateur sont :

Datum compress(Datum in)

Convertit un élément de données dans un format convenable pour un stockage physique dans un enregistrement feuille d'un index. Elle accepte une valeur de type spgConfigIn.attType et renvoie une valeur de type spgConfigOut.leafType. La valeur en sortie ne doit pas contenir un pointeur TOAST hors ligne.

Note : la mĂ©thode compress est seulement appliquĂ©e aux valeurs Ă  enregistrer. Les mĂ©thodes cohĂ©rentes reçoivent les clĂ©s de parcours non modifiĂ©es, sans transformation utilisant compress.

options

Définit un ensemble de paramÚtres visibles aux utilisateurs qui contrÎlent le comportement d'une classe d'opérateurs.

La dĂ©claration SQL de la fonction doit ressembler Ă  ceci :

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

La fonction options se voit donnĂ© un pointeur vers une structure local_relopts qui doit ĂȘtre remplie avec un ensemble d'options spĂ©cifiques Ă  la classe d'opĂ©rateur. Les options peuvent ĂȘtre accĂ©dĂ©es Ă  partir des autres fonctions de support en utilisant les macros PG_HAS_OPCLASS_OPTIONS() et PG_GET_OPCLASS_OPTIONS().

Étant donnĂ© que la reprĂ©sentation de la clĂ© dans SP-GiST est flexible, elle peut dĂ©pendre de paramĂštres spĂ©cifiĂ©s par l'utilisateur.

Toutes les mĂ©thodes permettant d'utiliser SP-GiST sont normalement exĂ©cutĂ©es dans un contexte mĂ©moire de courte durĂ©e, c'est-Ă -dire que CurrentMemoryContext sera remis Ă  zĂ©ro aprĂšs le traitement de chaque ligne. Il n'est cependant pas rĂ©ellement important de se soucier de dĂ©sallouer la mĂ©moire allouĂ©e avec palloc (la mĂ©thode config est une exception : elle essaiera d'Ă©viter les fuites mĂ©moire. Mais gĂ©nĂ©ralement, la mĂ©thode config ne nĂ©cessite rien si ce n'est assigner des constantes aux structures passĂ©es en paramĂštre).

Si la colonne indexée a un type de donnée collationnable, l'index de collationnement sera passé à toutes les méthodes, en utilisant le mécanisme standard PG_GET_COLLATION().

65.3.4. ImplĂ©mentation #

Cette section traite des détails d'implémentation et d'autres astuces qui sont utiles à connaßtre pour implémenter des opérateurs de classe SP-GiST.

65.3.4.1. Limites de SP-GiST #

Les lignes de feuille individuelles et les lignes intermĂ©diaires doivent tenir dans une unique page d'index (8 ko par dĂ©faut). Cependant, lorsque des donnĂ©es de taille variable sont indexĂ©es, les longues valeurs ne sont uniquement supportĂ©es que par les arbres suffixĂ©s, dans lesquels chaque niveau de l'arbre contient un prĂ©fixe qui est suffisamment petit pour tenir dans une page. La classe d'opĂ©rateurs doit uniquement dĂ©finir longValuesOK Ă  TRUE si elle supporte ce cas de figure. Dans le cas contraire, le cƓur de SP-GiST rejettera l'indexation d'une valeur plus large qu'une page.

De la mĂȘme maniĂšre, il est de la responsabilitĂ© de l'opĂ©rateur de classe de s'assurer que la taille des lignes intermĂ©diaires soit plus petite qu'une page ; cela limite le nombre de nƓuds enfants qui peuvent ĂȘtre utilisĂ©s dans une ligne intermĂ©diaire, ainsi que la taille maximum d'un prĂ©fixe.

Une autre limite est que lorsqu'un nƓud de ligne intermĂ©diaire pointe vers un ensemble de lignes de feuille, ces lignes doivent toutes ĂȘtre dans la mĂȘme page d'index (il s'agit d'une dĂ©cision d'architecture pour rĂ©duire le temps de recherche et utiliser moins de mĂ©moire dans les liens qui lient de telles lignes ensemble). Si l'ensemble de lignes de feuille grandit plus qu'une page, un dĂ©coupage est rĂ©alisĂ© et un nƓud intermĂ©diaire est insĂ©rĂ©. Pour que ce mĂ©canisme rĂ©solve le problĂšme, le nouveau nƓud intermĂ©diaire doit diviser l'ensemble de valeurs de feuilles en plus d'un groupe de nƓuds. Si la fonction picksplit de la classe d'opĂ©rateurs n'y parvient pas, le cƓur de SP-GiST met en Ɠuvre des mesures extraordinaires telles que dĂ©crites dans Section 65.3.4.3.

Quand longValuesOK vaut true, il est attendu que les niveaux successifs de l'arbre SP-GiST absorbera de plus en plus d'informations dans les prefixes et labels de noeuds des enregistrements internes, rendant la donnĂ©e feuille de plus en plus petite, pour qu'Ă  la fin, elle tienne sur un bloc. Pour empĂȘcher les bugs dans les classes d'opĂ©rateurs, du style boucle d'insertions infinies, le code principal de SP-GiST lĂšvera une erreur si la donnĂ©e feuille ne devient pas plus petite au bout de dix cycles d'appel Ă  la mĂ©thode choose.

Quand longValuesOK vaut true, il est attendu que les niveaux successifs de l'arbre SP-GiST absorberont de plus en plus d'informations dans les prĂ©fixes et labels de nƓuds des lignes internes, rendant la donnĂ©e requise pour la feuille de plus en plus petite, jusqu'Ă  ce qu'elle tienne sur un bloc. Pour empĂȘcher que des bugs dans les classes d'opĂ©rateurs causent des boucles d'insertion infinies, le noyau de SP-GiST lĂšvera une erreur si la donnĂ©e de la feuille ne devient pas plus petite dans les dix cycles d'appel Ă  la mĂ©thode choose.

65.3.4.2. SP-GiST sans label de nƓud #

Certains algorithmes d'arbres utilisent un ensemble de nƓuds figĂ© pour chaque ligne intermĂ©diaire ; par exemple, l'arbre quad-tree impose exactement quatre nƓuds correspondant aux quatre coins autour du centroĂŻde de la ligne intermĂ©diaire. Dans ce cas, le code travaille gĂ©nĂ©ralement avec les nƓuds au moyen de leur identifiant, et le besoin de label de nƓud ne se fait pas ressentir. Pour supprimer les labels de nƓud (et ainsi gagner de l'espace), la fonction picksplit peut retourner NULL pour le tableau nodeLabels, et de mĂȘme, la fonction choose peut retourner NULL pour le tableau prefixNodeLabels lors de l'action spgSplitTuple Cela aura pour effet d'obtenir une valeur NULL pour nodeLabels lors des appels aux fonctions choose et inner_consistent. En principe, les labels de nƓuds peuvent ĂȘtre utilisĂ©s par certaines lignes intermĂ©diaires, et ignorĂ©s pour les autres de mĂȘme index.

Lorsqu'une ligne intermĂ©daire sans label est concernĂ©, la fonction choose ne peut pas retourner spgAddNode car l'ensemble des nƓuds est supposĂ© ĂȘtre fixĂ© dans de tels cas.

65.3.4.3. Lignes intermĂ©diaires « All-the-same Â» #

Le cƓur de SP-GiST peut surcharger les rĂ©sultats de la fonction picksplit de l'opĂ©rateur de classe lorsque picksplit ne rĂ©ussit pas Ă  diviser la valeur de la feuille fournie en au moins un nƓud. Dans ce cas, la nouvelle ligne intermĂ©diaire est créée avec de multiples nƓuds qui ont tous le mĂȘme label (si un label est dĂ©fini) qui est celui attribuĂ© au nƓud utilisĂ© par picksplit et les valeurs des feuilles sont divisĂ©es alĂ©atoirement entre les nƓuds Ă©quivalents. Le drapeau allTheSame est activĂ© sur la ligne intermĂ©diaire pour signifier aux fonctions choose et inner_consistent que la ligne n'a pas l'ensemble de nƓud attendu.

Lorsque le cas d'une ligne allTheSame est rencontrĂ©, le rĂ©sultat de la fonction choose sous la forme spgMatchNode est interprĂ©tĂ© de maniĂšre Ă  ce que la nouvelle valeur puisse ĂȘtre assignĂ©e Ă  chacun des nƓuds Ă©quivalents ; le code du cƓur de SP-GiST ignorera la valeur nodeN fournie et descendra dans l'un des nƓuds enfants au hasard (pour conserver l'Ă©quilibre de l'arbre). Il s'agirait d'une erreur si la fonction choose retournait spgAddNode car tous les nƓuds ne seraient pas Ă©quivalent ; l'action spgSplitTuple doit ĂȘtre utilisĂ©e si la valeur Ă  insĂ©rer ne correspond pas aux nƓuds existants.

Lorsque le cas d'une ligne allTheSame est rencontrĂ©, la fonction inner_consistent peut tout autant retourner tous les nƓuds ou aucun des nƓuds ciblĂ©s pour continuer la recherche indexĂ©e car ils sont tous Ă©quivalents. Cela peut Ă©ventuellement nĂ©cessiter du code spĂ©cifique, suivant le support rĂ©alisĂ© par la fonction inner_consistent concernant la signification des nƓuds.

65.3.5. Exemples #

Les sources de PostgreSQL incluent plusieurs exemples de classes d'opĂ©rateurs d'index pour SP-GiST comme dĂ©crit dans Tableau 65.2. Lire le code dans src/backend/access/spgist/ et src/backend/utils/adt/.