10.2. Operadores #

El operador específico al que hace referencia una expresión de operador se determina mediante el siguiente procedimiento. Ten en cuenta que este procedimiento se ve afectado indirectamente por la precedencia de los operadores involucrados, ya que esto determinará qué subexpresiones se consideran las entradas de qué operadores. Consulta Section 4.1.6 para obtener más información.

Resolución de tipos de operadores

  1. Selecciona los operadores a considerar del catálogo del sistema pg_operator. Si se utilizó un nombre de operador no calificado por el esquema (el caso habitual), los operadores considerados son aquellos con el nombre y la cantidad de argumentos coincidentes que son visibles en la ruta de búsqueda actual (consulta Section 5.10.3). Si se proporcionó un nombre de operador calificado, sólo se consideran los operadores en el esquema especificado.

    1. Si la ruta de búsqueda encuentra múltiples operadores con tipos de argumentos idénticos, sólo se considera el que aparece primero en la ruta. Los operadores con tipos de argumentos diferentes se consideran en pie de igualdad independientemente de su posición en la ruta de búsqueda.

  2. Verifica si existe un operador que acepte exactamente los tipos de argumentos de entrada. Si existe uno (sólo puede haber una coincidencia exacta en el conjunto de operadores considerados), utilízalo. La falta de una coincidencia exacta crea un riesgo de seguridad al llamar, a través de un nombre calificado [9] (no habitual), a cualquier operador que se encuentre en un esquema que permita a usuarios no confiables crear objetos. En tales situaciones, realiza un cast sobre los argumentos para forzar una coincidencia exacta.

    1. Si un argumento de una invocación de operador binario es del tipo unknown, asume que es del mismo tipo que el otro argumento para esta comprobación. Las invocaciones que involucran dos entradas unknown, o un operador de prefijo con una entrada unknown, nunca encontrarán una coincidencia en este paso.

    2. Si un argumento de una invocación de operador binario es del tipo unknown y el otro es de un tipo de dominio, comprueba a continuación si hay un operador que acepte exactamente el tipo base del dominio en ambos lados; si es así, utilízalo.

  3. Busca la mejor coincidencia.

    1. Descarta los operadores candidatos para los cuales los tipos de entrada no coinciden y no se pueden convertir (utilizando una conversión implícita) para coincidir. Se asume que los literales unknown se pueden convertir a cualquier cosa para este propósito. Si sólo queda un candidato, utilízalo; de lo contrario, continúa con el siguiente paso.

    2. Si algún argumento de entrada es de un tipo de dominio, trátalo como si fuera del tipo base del dominio para todos los pasos siguientes. Esto asegura que los dominios actúen como sus tipos base a efectos de resolución de operadores ambiguos.

    3. Recorre todos los candidatos y conserva aquellos con la mayor cantidad de coincidencias exactas en los tipos de entrada. Conserva todos los candidatos si ninguno tiene coincidencias exactas. Si sólo queda un candidato, utilízalo; de lo contrario, continúa con el siguiente paso.

    4. Recorre todos los candidatos y conserva aquellos que acepten tipos preferidos (de la categoría de tipos del tipo de datos de entrada) en la mayor cantidad de posiciones donde se requiera conversión de tipos. Conserva todos los candidatos si ninguno acepta tipos preferidos. Si sólo queda un candidato, utilízalo; de lo contrario, continúa con el siguiente paso.

    5. Si algún argumento de entrada es unknown, comprueba las categorías de tipos aceptadas en esas posiciones de argumentos por los candidatos restantes. En cada posición, selecciona la categoría string si algún candidato acepta esa categoría. (Esta inclinación hacia string es apropiada ya que un literal de tipo desconocido se parece a una cadena). De lo contrario, si todos los candidatos restantes aceptan la misma categoría de tipo, selecciona esa categoría; de lo contrario, falla porque la elección correcta no se puede deducir sin más pistas. Ahora descarta los candidatos que no acepten la categoría de tipo seleccionada. Además, si algún candidato acepta un tipo preferido en esa categoría, descarta los candidatos que acepten tipos no preferidos para ese argumento. Conserva todos los candidatos si ninguno sobrevive a estas pruebas. Si sólo queda un candidato, utilízalo; de lo contrario, continúa con el siguiente paso.

    6. Si hay tanto argumentos unknown como de tipo conocido, y todos los argumentos de tipo conocido tienen el mismo tipo, asume que los argumentos unknown también son de ese tipo, y comprueba qué candidatos pueden aceptar ese tipo en las posiciones de argumentos unknown. Si exactamente un candidato supera esta prueba, utilízalo. De lo contrario, falla.

A continuación se muestran algunos ejemplos.

Example 10.1. Resolución de tipos del operador raíz cuadrada

Sólo hay un operador raíz cuadrada (prefijo |/) definido en el catálogo estándar, y toma un argumento de tipo double precision. El escáner asigna un tipo inicial de integer al argumento en esta expresión de consulta:

SELECT |/ 40 AS "square root of 40";
 square root of 40
-------------------
 6.324555320336759
(1 row)

De modo que el analizador realiza una conversión de tipo en el operando y la consulta es equivalente a:

SELECT |/ CAST(40 AS double precision) AS "square root of 40";


Example 10.2. Resolución de tipos del operador de concatenación de cadenas

Se utiliza una sintaxis similar a la de las cadenas para trabajar con tipos de cadenas y para trabajar con tipos de extensión complejos. Las cadenas con tipo no especificado se emparejan con los candidatos de operador probables.

Un ejemplo con un argumento no especificado:

SELECT text 'abc' || 'def' AS "text and unknown";

 text and unknown
------------------
 abcdef
(1 row)

En este caso, el analizador comprueba si hay un operador que tome text para ambos argumentos. Dado que existe, asume que el segundo argumento debería interpretarse como de tipo text.

Aquí tienes una concatenación de dos valores de tipos no especificados:

SELECT 'abc' || 'def' AS "unspecified";

 unspecified
-------------
 abcdef
(1 row)

En este caso no hay ninguna pista inicial sobre qué tipo usar, ya que no se especifican tipos en la consulta. Por lo tanto, el analizador busca todos los operadores candidatos y descubre que hay candidatos que aceptan tanto entradas de la categoría string como de la categoría bit-string. Dado que la categoría string es preferida cuando está disponible, se selecciona esa categoría, y luego se utiliza el tipo preferido para cadenas, text, como el tipo específico para resolver los literales de tipo desconocido.


Example 10.3. Resolución de tipos de operadores de valor absoluto y negación

El catálogo de operadores de PostgreSQL tiene varias entradas para el operador de prefijo @, todas las cuales implementan operaciones de valor absoluto para varios tipos de datos numéricos. Una de estas entradas es para el tipo float8, que es el tipo preferido en la categoría numérica. Por lo tanto, PostgreSQL utilizará esa entrada cuando se enfrente a una entrada unknown:

SELECT @ '-4.5' AS "abs";
 abs
-----
 4.5
(1 row)

Aquí el sistema ha resuelto implícitamente el literal de tipo desconocido como de tipo float8 antes de aplicar el operador elegido. Podemos verificar que se usó float8 y no algún otro tipo:

SELECT @ '-4.5e500' AS "abs";

ERROR:  "-4.5e500" is out of range for type double precision

Por lo lado, el operador de prefijo ~ (negación a nivel de bits) está definido sólo para tipos de datos enteros, no para float8. Por lo tanto, si intentamos un caso similar con ~, obtenemos:

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.

Esto sucede porque el sistema no puede decidir cuál de los varios operadores ~ posibles debería ser preferido. Podemos ayudarle con un cast explícito:

SELECT ~ CAST('20' AS int8) AS "negation";

 negation
----------
      -21
(1 row)


Example 10.4. Resolución de tipos del operador de inclusión de matrices

Aquí tienes otro ejemplo de cómo resolver un operador con una entrada conocida y otra desconocida:

SELECT array[1,2] <@ '{1,2,3}' as "is subset";

 is subset
-----------
 t
(1 row)

El catálogo de operadores de PostgreSQL tiene varias entradas para el operador infijo <@, pero las dos únicas que podrían aceptar una matriz de enteros en el lado izquierdo son la inclusión de matrices (anyarray <@ anyarray) y la inclusión de rangos (anyelement <@ anyrange). Dado que ninguno de estos pseudo-tipos polimórficos (consulta Section 8.21) se considera preferido, el analizador no puede resolver la ambigüedad sobre esa base. Sin embargo, el paso Step 3.f le indica que asuma que el literal de tipo desconocido es del mismo tipo que la otra entrada, es decir, una matriz de enteros. Ahora sólo uno de los dos operadores puede coincidir, por lo que se selecciona la inclusión de matrices. (Si se hubiera seleccionado la inclusión de rangos, habríamos obtenido un error, porque la cadena no tiene el formato correcto para ser un literal de rango).


Example 10.5. Operador personalizado en un tipo de dominio

A veces los usuarios intentan declarar operadores que se aplican únicamente a un tipo de dominio. Esto es posible pero no es tan útil como podría parecer, porque las reglas de resolución de operadores están diseñadas para seleccionar operadores que se aplican al tipo base del dominio. Como ejemplo considera

CREATE DOMAIN mytext AS text CHECK(...);
CREATE FUNCTION mytext_eq_text (mytext, text) RETURNS boolean AS ...;
CREATE OPERATOR = (procedure=mytext_eq_text, leftarg=mytext, rightarg=text);
CREATE TABLE mytable (val mytext);

SELECT * FROM mytable WHERE val = 'foo';

Esta consulta no utilizará el operador personalizado. El analizador comprobará primero si hay un operador mytext = mytext (Step 2.a), el cual no existe; luego considerará el tipo base del dominio, text, y comprobará si hay un operador text = text (Step 2.b), el cual sí existe; por lo que resuelve el literal de tipo unknown como text y utiliza el operador text = text. La única forma de conseguir que se utilice el operador personalizado es realizar un cast explícito sobre el literal:

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

de modo que el operador mytext = text se encuentre inmediatamente de acuerdo con la regla de coincidencia exacta. Si se llega a las reglas de mejor coincidencia, estas discriminan activamente los operadores en tipos de dominio. Si no lo hicieran, dicho operador crearía demasiados fallos de operador ambiguo, porque las reglas de casting siempre consideran que un dominio es convertible hacia o desde su tipo base, y por lo tanto el operador del dominio se consideraría utilizable en todos los mismos casos que un operador con nombre similar en el tipo base.




[9] El riesgo no surge con un nombre no calificado por el esquema, porque una ruta de búsqueda que contiene esquemas que permiten a usuarios no confiables crear objetos no es un patrón de uso seguro de esquemas.