Par défaut, une fonction est juste une « boîte noire » dont la base de données connait très peu le comportement. De ce fait, il peut arriver que les requêtes utilisant la fonction se trouvent exécutées beaucoup moins efficacement qu'elles ne pourraient. Il est possible de fournir une connaissance supplémentaire qui aide le planificateur à optimiser les appels de fonctions.
Quelques indications basiques peuvent être données via les annotations
déclaratives présentes dans la commande CREATE FUNCTION.
La plus importante de celles-ci est la
catégorie de volatilité (IMMUTABLE
, STABLE
,
ou VOLATILE
) ; on doit toujours être très soucieux
de la spécifier correctement lorsqu'on définit une fonction.
La propriété de sûreté face au parallélisme (PARALLEL
UNSAFE
, PARALLEL RESTRICTED
, ou
PARALLEL SAFE
) doit également être spécifiée
pour espérer utiliser la fonction dans des requêtes parallélisées.
Il peut aussi être utile de spécifier le coût estimé de l'exécution
de la fonction, et le nombre de lignes qu'une fonction
renvoyant un ensemble d'enregistrements est estimée renvoyer.
Toutefois, la manière déclarative de spécifier ces deux informations
ne permet seulement que de donner une valeur constante, ce qui est
souvent inadéquat.
Il est aussi possible de rattacher une fonction de support de planification à une fonction appelable en SQL (appelée fonction cible), et ainsi de fournir sur la fonction cible une connaissance qui serait trop complexe à être représentée déclarativement. Les fonctions de support de planification doivent être écrites en C (alors que leurs fonctions cibles peuvent ne pas l'être), ce qui en fait une fonctionnalité avancée que relativement peu de personnes utiliseront.
Une fonction de support de planification doit avoir la signature SQL
supportfn(internal) returns internal
Elle est rattachée à sa fonction cible en spécifiant la clause
SUPPORT
dans la création de la fonction cible.
Les détails de l'API des fonctions de support de planification
se trouvent dans le fichier
src/include/nodes/supportnodes.h
dans le code
source de PostgreSQL. Ici, on ne fournit
qu'une vue d'ensemble de ce que les fonctions de support de planification
peuvent faire.
L'ensemble des demandes possibles adressables à une fonction de support
est extensible, si bien que d'autres choses seront possibles dans
des versions ultérieures.
Certains appels de fonctions peuvent être simplifiés lors de la
planification en se basant sur les propriétés spécifiques de la fonction.
Par exemple, int4mul(n, 1)
pourrait être simplifié par
n
. Ce type de transformation peut être exécuté
par une fonction de support de planification, en lui faisant implémenter
le type de demande SupportRequestSimplify
.
La fonction de support va être appelée pour chaque instance de la
fonction cible trouvée dans l'arbre d'analyse de la requête.
Si elle trouve qu'un appel particulier peut être simplifié en
une autre forme, elle est capable de construire et renvoyer
un arbre d'analyse représentant cette expression. Cela fonctionnera
automatiquement pour les opérateurs basés sur cette fonction,
également -- dans l'exemple juste ci-dessus, n * 1
serait aussi simplifié en n
.
(Mais notez que c'est juste un exemple ; cette optimisation particulière
n'est pas réellement mise en oeuvre par PostgreSQL.)
Nous ne garantissons pas que PostgreSQL n'appelera
jamais la fonction cible dans les cas que la fonction de support pourrait
simplifier. Assurez-vous d'une équivalence rigoureuse entre l'expression
simplifiée et l'exécution réelle de la fonction cible.
Pour les fonctions cible qui renvoient un booléen, il est souvent utile
d'estimer la fraction des lignes qui vont être sélectionnées par une clause
WHERE
utilisant cette fonction. Ceci est réalisable avec
une fonction de support qui implémente le type de demande
SupportRequestSelectivity
.
Si le temps d'exécution d'une fonction cible est très dépendant
de ses entrées, il peut être utile de fournir un coût d'exécution
non constant pour celle-ci.
Ceci est réalisable avec une fonction de support implémentant
le type de demande SupportRequestCost
.
Pour les fonctions cibles qui renvoient des ensembles de lignes,
il est souvent utile de fournir une estimation non constante du
nombre de lignes renvoyées.
Ceci est réalisable avec une fonction de support implémentant
le type de demande SupportRequestRows
.
Pour les fonctions cibles qui renvoient un booléen, il est envisageable
de convertir un appel de fonction au niveau d'un WHERE
vers une ou
plusieurs clauses d'un opérateur indexable. Ces clauses peuvent être
exactement équivalentes à la condition de la fonction, ou bien elle peuvent
être plus faibles (c'est-à-dire qu'elles peuvent accepter certaines
valeurs que la condition via la fonction n'accepte pas).
Dans ce dernier cas, la condition d'index est dite
avec perte; elle peut toujours être utilisée
pour un parcours d'index, mais la fonction devra être appelée pour chaque
ligne renvoyée par l'index pour vérifier qu'elle satisfait la condition
WHERE
.
Pour créer de telles conditions, la fonction de support doit implémenter
le type de demande SupportRequestIndexCondition
.