Documentation PostgreSQL 14.22 » Internes » Index GIN » Extensibilité

Précédent | Niveau supérieur | Suivant
67.2. Classes d'opérateur natives | Sommaire | 67.4. Implantation

## 67.3. Extensibilité

L'interface GIN a un haut niveau d'abstraction. De ce fait, la personne qui code la méthode d'accès n'a besoin d'implanter que les sémantiques du type de données accédé. La couche GIN prend en charge la gestion de la concurrence, des traces et des recherches dans la structure de l'arbre.

Pour développer une méthode d'accès GIN fonctionnelle, il suffit d'implanter quelques méthodes utilisateur. Celles-ci définissent le comportement des clés dans l'arbre et les relations entre clés, valeurs d'éléments indexées et requêtes indexables. En résumé, GIN combine extensibilité, généricité, réutilisation du code, et une interface claire.

Voici les deux méthodes qu'une classe d'opérateurs GIN doit fournir sont :

`Datum *extractValue(Datum inputValue, int32 *nkeys, bool **nullFlags)`

  Retourne un tableau de clés alloué par palloc en fonction d'un élément à indexer. Le nombre de clés renvoyées doit être stocké dans `*nkeys`. Si une des clés peut être nulle, allouez aussi par palloc un tableau de `*nkeys` champs de type `bool`, stockez son adresse dans `*nullFlags`, et positionnez les drapeaux null où ils doivent l'être. `*nullFlags` peut être laissé à `NULL` (sa valeur initiale) si aucune clé n'est nulle. La valeur retournée peut être `NULL` si l'élément ne contient aucune clé.

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

  Renvoie un tableau de clés en fonction de la valeur à rechercher ; c'est-à-dire que `query` est la valeur du côté droit d'un opérateur indexable dont le côté gauche est la colonne indexée. `n` est le numéro de stratégie de l'opérateur dans la classe d'opérateurs (voir Section 38.16.2). Souvent, `extractQuery` doit consulter `n` pour déterminer le type de données de `query` et la méthode à utiliser pour extraire les valeurs des clés. Le nombre de clés renvoyées doit être stocké dans `*nkeys`. Si une des clés peut être nulle, allouez aussi par palloc un tableau de `*nkeys` champs de type `bool`, stockez son adresse dans `*nullFlags`, et positionnez les drapeaux `NULL` où ils doivent l'être. `*nullFlags` peut être laissé à NULL (sa valeur initiale) si aucune clé n'est nulle. La valeur de retour peut être `NULL` si `query` ne contient aucune clé.

  `searchMode` est un argument de sortie qui permet à `extractQuery` de spécifier des détails sur comment la recherche sera effectuée. Si `*searchMode` est positionné à `GIN_SEARCH_MODE_DEFAULT` (qui est la valeur à laquelle il est initialisé avant l'appel), seuls les éléments qui correspondent à au moins une des clés retournées sont considérés comme des candidats de correspondance. Si `*searchMode` est positionné à `GIN_SEARCH_MODE_INCLUDE_EMPTY`, alors en plus des éléments qui contiennent au moins une clé correspondante, les éléments qui ne contiennent aucune clé sont aussi considérées comme des candidats de correspondance. (Ce mode est utile pour implémenter un opérateur « est le sous-ensemble de », par exemple.) Si `*searchMode` est positionné à `GIN_SEARCH_MODE_ALL`, alors tous les éléments non nuls de l'index sont candidats de correspondance, qu'ils aient une clé qui corresponde à celles retournées ou non. (Ce mode est beaucoup plus lent que les deux autres, mais il peut être nécessaire pour implanter des cas exceptionnels correctement. Un opérateur qui a besoin de ce mode dans la plupart des cas n'est probablement pas un bon candidat pour une classe d'opérateurs GIN.) Les symboles à utiliser pour configurer ce mode sont définis dans `access/gin.h`.

  `pmatch` est un argument de sortie à utiliser quand une correspondance partielle est permise. Pour l'utiliser, `extractQuery` doit allouer un tableau de booléens `*nkeys` et stocker son adresse dans `*pmatch`. Chaque élément du tableau devrait être positionné à true si la clé correspondante nécessite une correspondance partielle, et à false sinon. Si `*pmatch` est configuré à `NULL`, alors GIN suppose qu'une correspondance partielle n'est pas nécessaire. La variable est initialisée à `NULL` avant l'appel, et peut donc être simplement ignorée par les classes d'opérateurs qui ne gèrent pas les correspondances partielles.

  `extra_data` est un argument de sortie qui autorise `extractQuery` à passer des données supplémentaires aux méthodes `consistent` et `comparePartial`. Pour l'utiliser, `extractQuery` doit allouer un tableau de pointeurs `*nkeys` et stocker son adresse dans `*extra_data`, puis stocker ce qu'il souhaite dans les pointeurs individuels. La variable est initialisée à `NULL` avant l'appel, afin que ce paramètre soit simplement ignoré par une classe d'opérateurs qui n'a pas besoin de données supplémentaires. Si `*extra_data` est positionné, le tableau dans son ensemble est passé à la méthode `consistent`, et l'élément approprié à la méthode `comparePartial`.

Une classe d'opérateurs doit aussi fournir une fonction pour vérifier si un élément indexé correspond à la requête. Elle vient en deux versions, une fonction booléenne `consistent` et une fonction ternaire `triConsistent`. Cette dernière couvre les fonctionnalités des deux, donc fournir uniquement `triConsistent` est suffisant. Cependant, si la variante booléenne est bien moins coûteuse à calculer, il peut être avantageux de fournir les deux. Si seule la variante booléenne est fournie, certaines optimisations dépendant de la réfutation d'éléments d'index avant de récupérer toutes les clés sont désactivées.

`bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])`

  Retourne true si un élément indexé répond à l'opérateur de requête possédant le numéro de stratégie `n` (ou pourrait le satisfaire si l'indication recheck est retournée). Cette fonction n'a pas d'accès direct aux valeurs des éléments indexés. Au lieu de cela, ce qui est disponible, c'est la connaissance de quelles valeurs de clés extraites de la requête apparaissent dans un élément indexé donné. Le tableau `check` a une longueur de `nkeys`, qui est identique au nombre de clés retourné précédemment par `extractQuery` pour cette donnée `query`. Chaque élément du tableau `check` est true si l'élément indexé contient la clé de requête correspondante, c'est à dire, si (check[i] == true), la i-ème clé du tableau résultat de `extractQuery` est présente dans l'élément indexé. La donnée `query` originale est passée au cas où la méthode `consistent` aurait besoin de le consulter, de même que les tableaux `queryKeys[]` et `nullFlags []` retournés précédemment par `extractQuery`. `extra_data` est le tableau de données supplémentaires renvoyé par `extractQuery`, ou `NULL` si aucun.

  Quand `extractQuery` renvoie une clé nulle dans `queryKeys[]`, l'élément correspondant de `check[]` est true si l'élément indexé contient une clé nulle ; c'est-à-dire que la sémantique de `check []` est comme celle de `IS NOT DISTINCT FROM`. La fonction `consistent` peut examiner l'élément correspondant de `nullFlags[]` si elle a besoin de faire la différence entre une correspondance de valeur « normale » et une correspondance nulle.

  En cas de réussite, `*recheck` devrait être positionné à true si les enregistrements de la table doivent être revérifiées par rapport à l'opérateur de la requête, ou à false si le test d'index est exact. Autrement dit, une valeur de retour à false garantit que l'enregistrement de la table ne correspond pas ; une valeur de retour à true avec `*recheck` à false garantit que l'enregistrement de la table correspond à la requête ; et une valeur de retour à true avec `*recheck` à true signifie que l'enregistrement de la table pourrait correspondre à la requête, et qu'il doit être récupéré et re-vérifié en évaluant l'opérateur de la requête directement sur l'élément initialement indexé.

`GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])`

  `triConsistent` est similaire à `consistent`, mais en lieu de booléens dans le vecteur `check`, il y a trois valeurs possibles pour chaque clé : `GIN_TRUE`, `GIN_FALSE` et `GIN_MAYBE`. `GIN_FALSE` et `GIN_TRUE` ont la même signification que des valeurs booléennes standards alors que `GIN_MAYBE` signifie que la présence de cette clé est inconnue. Quand des valeurs `GIN_MAYBE` sont présentes, la fonction devrait seulement renvoyer `GIN_TRUE` si l'élément correspond à coup sûr que l'élément de l'index contient ou non les clés de la requête correspondante. De la même façon, la fonction doit renvoyer `GIN_FALSE` seulement si l'élément ne correspond pas, qu'il contienne ou non des clés `GIN_MAYBE`. Si le résultat dépend des entrées `GIN_MAYBE`, autrement dit si la correspondance ne peut pas être confirmée ou réfutée en se basant sur les clés connues de la requête, la fonction doit renvoyer `GIN_MAYBE`.

  Quand il n'y a pas de valeurs `GIN_MAYBE` dans le vecteur `check`, la valeur de retour `GIN_MAYBE` est équivalent à configurer l'indicateur `recheck` dans la fonction booléenne `consistent`.

De plus, GIN doit avoir un moyen de trier les valeurs des clés stockées dans l'index. La classe d'opérateurs peut définir l'ordre de tri en spécifiant une méthode de comparaison :

`int compare(Datum a, Datum b)`

  Compare deux clés (pas des éléments indexés) et renvoie un entier inférieur à zéro, égal à zéro ou supérieur à zéro, indiquant si la première clé est inférieure à, égale à ou supérieure à la seconde. Les clés nulles ne sont jamais fournies en argument à cette fonction.

Sinon, si la classe d'opérateurs ne fournit pas de méthode `compare`, GIN cherchera la classe d'opérateurs B-Tree par défaut pour le type de donnée de la clé d'index, et utilisera sa fonction de comparaison. Il est recommandé de spécifier la fonction de comparaison dans une classe d'opérateurs GIN destinée à un seul type de donnée, car rechercher la classe d'opérateurs B-Tree coûte quelques cycles CPU. Cependant, les classes d'opérateurs GIN polymorphiques (telle que `array_ops`) ne peuvent typiquement pas spécifier une seule fonction de comparaison.

Une classe d'opérateurs pour GIN peut fournir en option les méthodes suivantes :

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

  Compare une clé d'une requête de correspondance partielle à une clé d'index. Renvoie un entier dont le signe indique le résultat : inférieur à zéro signifie que la clé d'index ne correspond pas à la requête mais que le parcours d'index va continuer ; zéro signifie que la clé d'index ne correspond pas à la requête ; supérieur à zéro indique que le parcours d'index doit s'arrêter car il n'existe pas d'autres correspondances. Le numéro de stratégie `n` de l'opérateur qui a engendré la requête de correspondance partielle est fourni au cas où sa sémantique serait nécessaire pour déterminer la fin du parcours. De plus, `extra_data` est l'élément correspondant du tableau renvoyé par `extractQuery` (`NULL` sinon). Les clés nulles ne sont jamais passées à cette fonction.

`void options(local_relopts *relopts)`

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

  La fonction `options` se voit donner un pointeur vers une structure `local_relopts` qui doit être remplie avec un ensemble d'options spécifiques à la classe d'opérateurs. 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 l'extraction des clés des valeurs indexées et la représentation de la clé dans GIN sont flexibles, elles peuvent dépendre de paramètres spécifiés par l'utilisateur.

Pour supporter des requêtes à « correspondance partielle », une classe d'opérateurs doit fournir la méthode `comparePartial`, et sa méthode `extractQuery` doit positionner le paramètre `pmatch` quand une requête à correspondance partielle est rencontrée. Voir Section 67.4.2 pour les détails.

Le type de données réel des différentes valeurs `Datum` mentionnées ci-dessus varient en fonction de la classe d'opérateurs. Les valeurs d'élément passées à `extractValue` sont toujours du type en entrée de la classe d'opérateurs, et toutes les valeurs de clé doivent être du type `STORAGE` de la classe. Le type de l'argument `query` passé aux fonctions `extractQuery`, `consistent` et `triConsistent` est le type de l'argument côté droit de l'opérateur du membre de la classe identifié par le numéro de stratégie. Ce n'est pas nécessairement le même que l'élément indexé, tant que des valeurs de clés d'un type correct peuvent en être extraites. Néanmoins, il est recommandé que les déclarations SQL de ces trois fonctions de support utilisent le type de données indexé de la classe d'opérateurs pour l'argument `query`, même si le type réel pourrait être différent suivant l'opérateur.