PostgreSQLLa base de données la plus sophistiquée au monde.

Version anglaise

10.2. Opérateurs

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, « Précédence d'opérateurs » pour plus d'informations.

Procédure 10.1. Résolution de types pour les opérateurs

  1. 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.7.3, « Chemin de parcours des schémas »). Si un nom qualifié d'opérateur a été donné, seuls les opérateurs dans le schéma spécifié sont examinés.

    1. 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.

  2. 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.

    1. 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.

    2. Si un argument de l'appel d'une invocation d'opérateur binaire est de type unknown et que l'autre est un domaine, alors vérifier s'il existe un type acceptant exactement le type de base du domaine sur les deux côtés et, dans ce cas, l'utiliser.

  3. Rechercher la meilleure correspondance.

    1. 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.

    2. Si un des arguments est de type domaine, le traiter comme le type de base du domaine pour les étapes suivantes. Ceci assure que les domaines agissent comme leur type de base dans le cas de résolution d'opérateur ambigue.

    3. 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.

    4. 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.

    5. 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.

    6. 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.

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.19, « Pseudo-Types ») n'est considéré comme préféré, l'analyseur ne peut pas résoudre l'ambiguité sur cette base. Néanmoins, la règle de la dernière résolution 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 type domaine

Quelque fois, les utilisateurs essaient de déclarer des opérateurs s'appliquant uniquement sur un type domaine. Ceci est possible mais n'est pas aussi utile que cela paraît parce que les règles de résolution d'opérateur sont conçues pour sélectionner les opérateurs qui s'appliquent au type de base du domaine. En voici un exemple :

CREATE DOMAIN montexte AS text CHECK(...);
CREATE FUNCTION montexte_eq_text (montexte, text) RETURNS boolean AS ...;
CREATE OPERATOR = (procedure=montexte_eq_text, leftarg=montexte, rightarg=text);
CREATE TABLE matable (val montexte);

SELECT * FROM matable WHERE val = 'foo';

Cette requête n'utilisera pas l'opérateur personnalisé. L'analyseur va tout d'abord voir s'il existe un opérateur montexte = montexte (Étape 2.a). Comme il n'existe pas, il considérera le type de base du domaine, text, et cherchera un opérateur text = text (Étape 2.b), qui, lui, existe ; donc il résout le litéral de type unknown en type text et utilise l'opérateur text = text. La seule façon d'obtenir l'utilisation de l'opérateur spécialisé est de convertir explicitement le litéral :

SELECT * FROM matable WHERE val = text 'foo';

pour que l'opérateur montexte = text soit trouvé immédiatement suivant les règles de correspondance exacte. Si ces règles offrent une correspondance exacte, elles discriminent activement les opérateurs sur des types domaines. Dans le cas contraire, ce type d'opérateur pourrait créer trop d'échecs à cause d'opérateurs ambigues car les règles de conversion considèrent toujours un domaine comme convertissable vers ou à partir du type de base, et donc l'opérateur sur le domaine serait considéré comme utilisable sur tous les cas où un opérateur de même nom sur le type de base le serait.