L'opérateur spécifique qui est référence par une expression d'opérateur est déterminé par la procédure ci-dessous. Notez que cette procédure est indirectement affectée par l'ordre d'insertion des opérateurs car cela va déterminer les sous-expressions prises en entrée des opérateurs. Voir la Section 4.1.6 pour plus d'informations.
Résolution de types pour les opérateurs
Sélectionner les opérateurs à examiner depuis le catalogue système
pg_operator
. Si un nom non-qualifié
d'opérateur était utilisé (le cas habituel), les opérateurs examinés
sont ceux avec un nom et un nombre d'arguments corrects et qui sont
visibles dans le chemin de recherche courant (voir la
Section 5.8.3). Si un nom qualifié d'opérateur
a été donné, seuls les opérateurs dans le schéma spécifié sont
examinés.
Si un chemin de recherche trouve de nombreux opérateurs avec des types d'arguments identiques, seul sera examiné celui apparaissant le plus tôt dans le chemin. Mais les opérateurs avec des types d'arguments différents sont examinés sur une base d'égalité indépendamment de leur position dans le chemin de recherche.
Vérifier que l'opérateur accepte le type exact des arguments en entrée. Si un opérateur existe (il peut en avoir uniquement un qui corresponde exactement dans l'ensemble des opérateurs considérés), utiliser cet opérateur. Le manque d'une correspondance exacte crée un risque de sécurité lors de l'appel, via un nom qualifié [8] (non typique), de tout opérateur trouvé dans un schéma permettant à des utilisateurs sans confiance de créer des objets. Dans de telles situations, convertissez les arguments pour forcer une correspondance exacte.
Si un argument lors d'une invocation d'opérateur binaire est de type
unknown
(NdT : inconnu), alors considérer pour ce contrôle
que c'est le même type que l'autre argument. Les invocations impliquant deux
entrées de type unknown
, ou un opérateur unitaire avec en entrée
une donnée de type unknown
ne trouveront jamais une correspondance
à ce niveau.
Si un argument d'un opérateur binaire est de type unknown
et que
l'autre est un domaine, vérifier ensuite s'il existe un opérateur qui accepte
le type de base du domaine des deux côtés ; si c'est le cas, l'utiliser.
Rechercher la meilleure correspondance.
Se débarrasser des opérateurs candidats pour lesquels les types en
entrée ne correspondent pas et qui ne peuvent pas être convertis (en
utilisant une conversion implicite) dans le type correspondant.
Le type unknown
est supposé être convertible vers tout.
Si un candidat reste, l'utiliser, sinon aller à la
prochaine étape.
Si l'argument en entrée est d'un type de domaine, le traiter comme étant le type de base du domaine pour les étapes suivantes. Ceci nous assure que les domaines se comportent comme leur type de base pour la résolution d'opérateurs ambigus.
Parcourir tous les candidats et garder ceux avec la correspondance la plus exacte par rapport aux types en entrée. Garder tous les candidats si aucun n'a de correspondance exacte. Si un seul candidat reste, l'utiliser ; sinon, aller à la prochaine étape.
Parcourir tous les candidats et garder ceux qui acceptent les types préférés (de la catégorie des types de données en entrée) aux positions où la conversion de types aurait été requise. Garder tous les candidats si aucun n'accepte les types préférés. Si seulement un candidat reste, l'utiliser ; sinon aller à la prochaine étape.
Si des arguments en entrée sont unkown
, vérifier la
catégorie des types acceptés à la position de ces arguments par les
candidats restants. À chaque position, sélectionner la catégorie
chaîne de caractères
si un des candidats accepte cette
catégorie (cette préférence vers les chaînes de caractères est
appropriée car le terme type-inconnu ressemble à une chaîne de
caractères). Dans le cas contraire, si tous les candidats restants
acceptent la même catégorie de types, sélectionner cette catégorie.
Dans le cas contraire, échouer car le choix correct ne peut pas être
déduit sans plus d'indices. Se débarrasser maintenant des candidats qui
n'acceptent pas la catégorie sélectionnée. De plus, si des candidats
acceptent un type préféré de cette catégorie, se débarrasser des
candidats qui acceptent, pour cet argument, les types qui ne sont pas
préférés.
Conserver tous les candidats si aucun ne survit à ces tests. Si un
candidat survit, utilisez-le ; sinon continuer avec l'étape suivante.
S'il y a des arguments à fois unkown
et connus, et que tous
les arguments de type connu ont le même type, supposer que les arguments
unkown
sont de ce même type, et vérifier les candidats
qui acceptent ce type aux positions des arguments de type unknown
.
Si un seul candidat réussit ce test, utilisez-le. Sinon, échec.
Ceci crée un risque de sécurité lors de l'appel, via un nom qualifié
[9],
d'une fonction à nombre d'arguments variant trouvée dans un schéma permettant
aux utilisateurs de créer des objets. Un utilisateur mal intentionné peut
prendre contrôle et exécuter des fonctions SQL comme si vous les aviez
exécuté. Remplacer un appel utilisant le mot clé VARIADIC
,
qui contourne ce risque. Les appels utilisant des paramètres VARIADIC
"any"
n'ont généralement pas de formulation équivalente contenant le
mot clé VARIADIC
. Pour exécuter ces appels en toute
sécurité, le schéma de la fonction doit n'autoriser la création d'objets
qu'aux utilisateurs de confiance.
Quelques exemples suivent.
Exemple 10.1. Résolution du type d'opérateur factoriel
Il n'existe qu'un seul opérateur factoriel (!
postfix)
défini dans le catalogue standard. Il prend un argument de type
bigint
. Le scanner affecte au début le type integer
à l'argument dans cette expression :
SELECT 40 ! AS "40 factorial"; 40 factorial -------------------------------------------------- 815915283247897734345611269596115894272000000000 (1 row)
L'analyseur fait donc une conversion de types sur l'opérande et la requête est équivalente à
SELECT CAST(40 AS bigint) ! AS "40 factorial";
Exemple 10.2. Résolution de types pour les opérateurs de concaténation de chaînes
La syntaxe d'une chaîne de caractères est utilisée pour travailler avec les types chaînes mais aussi avec les types d'extensions complexes. Les chaînes de caractères avec un type non spécifié sont comparées avec les opérateurs candidats probables.
Un exemple avec un argument non spécifié :
SELECT text 'abc' || 'def' AS "text and unknown"; text and unknown ------------------ abcdef (1 row)
Dans ce cas, l'analyseur cherche à voir s'il existe un opérateur
prenant text
pour ses deux arguments. Comme il y en a,
il suppose que le second argument devra être interprété comme un type
text
.
Voici une concaténation sur des valeurs de type non spécifié :
SELECT 'abc' || 'def' AS "unspecified"; unspecified ------------- abcdef (1 row)
Dans ce cas, il n'y a aucune allusion initiale sur quel type utiliser
puisqu'aucun type n'est spécifié dans la requête. Donc, l'analyseur
regarde pour tous les opérateurs candidats et trouve qu'il existe des
candidats acceptant en entrée la catégorie chaîne de caractères
(string) et la catégorie morceaux de chaînes (bit-string). Puisque la
catégorie chaînes de caractères est préférée quand elle est disponible,
cette catégorie est sélectionnée. Le type préféré pour la catégorie
chaînes étant text
, ce type est utilisé comme le type
spécifique pour résoudre les types inconnus.
Exemple 10.3. Résolution de types pour les opérateurs de valeur absolue et de négation
Le catalogue d'opérateurs de PostgreSQL a
plusieurs entrées pour l'opérateur de préfixe @
. Ces entrées
implémentent toutes des opérations de valeur absolue pour des types de
données numériques variées. Une de ces entrées est pour le type
float8
(réel) qui est le type préféré dans la catégorie
des numériques. Par conséquent, PostgreSQL
utilisera cette entrée quand il sera en face d'un argument de type
unknown
:
SELECT @ '-4.5' AS "abs"; abs ----- 4.5 (1 row)
Le système a compris implicitement que le litéral de type
unknown
est de type float8
(réel) avant d'appliquer l'opérateur choisi. Nous
pouvons vérifier que float8
, et pas un autre type, a été
utilisé :
SELECT @ '-4.5e500' AS "abs"; ERROR: "-4.5e500" is out of range for type double precision
D'un autre côté, l'opérateur préfixe ~
(négation bit par bit) est
défini seulement pour les types entiers et non pas pour
float8
(réel). Ainsi, si nous essayons un cas similaire
avec ~
, nous obtenons :
SELECT ~ '20' AS "negation"; ERROR: operator is not unique: ~ "unknown" HINT: Could not choose a best candidate operator. You might need to add explicit type casts.
Ceci se produit parce que le système ne peut pas décider quel opérateur
doit être préféré parmi les différents opérateurs ~
possibles. Nous pouvons l'aider avec une conversion explicite :
SELECT ~ CAST('20' AS int8) AS "negation"; negation ---------- -21 (1 row)
Exemple 10.4. Résolution du type d'opérateur avec des inclusions de tableaux
Voici un autre exemple de résolution d'un opérateur avec une entrée de type connu et une entrée de type inconnu :
SELECT array[1,2] <@ '{1,2,3}' as "is subset"; is subset ----------- t (1 row)
Le catalogue d'opérateurs pour PostgreSQL dispose de
plusieurs entrées pour un opérateur <@
, mais les deux seuls
qui peuvent accepter un tableau d'entiers en argument gauche sont ceux d'inclusion
de tableaux (anyarray
<@
anyarray
)
et d'inclusion d'intervalles
(anyelement
<@
anyrange
).
Comme aucun de ces pseudo-types polymorphiques (voir Section 8.20) n'est considéré comme préféré, l'analyseur ne peut
pas résoudre l'ambiguité sur cette base. Néanmoins, Étape 3.f
dit de supposer que le litéral de type inconnu est du même type que
l'autre entrée, c'est-à-dire dans cet exemple le tableau d'entiers. Maintenant
seul un des deux opérateurs peut correspondre, donc l'inclusion de tableaux
est sélectionné. (Si l'inclusion d'intervalles avait été sélectionnée, nous
aurions obtenu une erreur car la chaîne n'a pas le bon format pour une intervalle.)
Exemple 10.5. Opérateur personnalisé sur un domaine
Les utilisateurs essaient parfois de déclarer des opérateurs s'appliquant juste à un domaine. Ceci est possible mais pas aussi intéressant que cela paraît car les règles de résolution des opérateurs sont conçues pour sélectionner des opérateurs s'appliquant au type de base du domaine. Voici un exemple :
CREATE DOMAIN mon_texte AS text CHECK(...); CREATE FUNCTION mon_texte_eq_text (mon_texte, text) RETURNS boolean AS ...; CREATE OPERATOR = (procedure=mon_texte_eq_text, leftarg=mon_texte, rightarg=text); CREATE TABLE ma_table (val mon_texte); SELECT * FROM ma_table WHERE val = 'foo';
Cette dernière requête n'utilisera pas l'opérateur personnalisé. L'analyseur
verra tout d'abord s'il existe un opérateur mon_texte
=
mon_texte
(Étape 2.a), qui n'existe pas ; puis
il considérera le type de base du domaine et verra s'il existe un opérateur
text
=
text
(Étape 2.b), ce qui est vrai ; donc il résout
le litéral de type unknown
comme un type text
et
utilise l'opérateur text
=
text
.
La seule façon d'obtenir l'opérateur personnalisé à utiliser est de convertir
explicitement la valeur litérale :
SELECT * FROM ma_table WHERE val = text 'foo';
de façon à ce que l'opérateur mon_texte
=
text
est immédiatement trouvé suivant la règle de correspondance
exacte. Si les règles de meilleure correspondance sont atteintes, elles
discriminent complètement contre les opérateurs sur les domaines. Dans le cas
contraire, un tel opérateur créerait trop d'échecs sur des opérateurs ambigus
car les règles de conversion considèrent en permanence un domaine comme réduisible
à son type de base, et de ce fait, l'opérateur du domaine serait considéré comme
utilisable dans les mêmes cas qu'un opérateur de même nom sur le type de base.
[8] Le risque ne vient pas d'un nom sans qualification par le schéma car un chemin de recherche contenant des schémas permettant à des utilisateurs sans confiance de créer des objets n'est pas une méthode sécurisée d'utilisation des schémas.
[9] The hazard does not arise with a non-schema-qualified name, because a search path containing schemas that permit untrusted users to create objects is not a secure schema usage pattern.