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.
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
Nom | Opérateurs indexables | Opé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.
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.
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()
.
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.
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
.
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.
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.
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/
.