44.2. Valores de datos #

44.2.1. Mapeo de tipos de datos
44.2.2. Null, None
44.2.3. Arrays, Lists
44.2.4. Tipos compuestos
44.2.5. Funciones que devuelven conjuntos (Set-Returning)

En general, el objetivo de PL/Python es proporcionar un mapeo natural entre el mundo de PostgreSQL y el de Python. Esto informa las reglas de mapeo de datos que se describen a continuación.

44.2.1. Mapeo de tipos de datos #

Cuando se llama a una función PL/Python, sus argumentos se convierten de su tipo de datos de PostgreSQL al tipo de Python correspondiente:

  • El tipo boolean de PostgreSQL se convierte al tipo bool de Python.

  • Los tipos smallint, int, bigint y oid de PostgreSQL se convierten al tipo int de Python.

  • Los tipos real y double de PostgreSQL se convierten al tipo float de Python.

  • El tipo numeric de PostgreSQL se convierte al tipo Decimal de Python. Este tipo se importa del paquete cdecimal si está disponible. De lo contrario, se usará decimal.Decimal de la biblioteca estándar. cdecimal es significativamente más rápido que decimal. Sin embargo, en Python 3.3 y versiones posteriores, cdecimal se ha integrado en la biblioteca estándar bajo el nombre decimal, por lo que ya no hay ninguna diferencia.

  • El tipo bytea de PostgreSQL se convierte al tipo bytes de Python.

  • Todos los demás tipos de datos, incluidos los tipos de cadenas de caracteres de PostgreSQL, se convierten a un tipo str de Python (en Unicode como todas las cadenas de Python).

  • Para los tipos de datos no escalares, consulta más abajo.

Cuando una función PL/Python retorna, su valor de retorno se convierte al tipo de datos de retorno de PostgreSQL declarado de la siguiente manera:

  • Cuando el tipo de retorno de PostgreSQL es boolean, el valor de retorno se evaluará para determinar su veracidad de acuerdo con las reglas de Python. Es decir, 0 y la cadena vacía son falsos, pero cabe destacar que 'f' es verdadero.

  • Cuando el tipo de retorno de PostgreSQL es bytea, el valor de retorno se convertirá al tipo bytes de Python utilizando las funciones incorporadas de Python correspondientes, y el resultado se convertirá a bytea.

  • Para todos los demás tipos de retorno de PostgreSQL, el valor de retorno se convierte a una cadena utilizando la función incorporada str de Python, y el resultado se pasa a la función de entrada del tipo de datos de PostgreSQL. (Si el valor de Python es un tipo float, se convierte utilizando la función incorporada repr en lugar de str, para evitar la pérdida de precisión).

    Las cadenas se convierten automáticamente a la codificación del servidor PostgreSQL cuando se pasan a PostgreSQL.

  • Para los tipos de datos no escalares, consulta más abajo.

Ten en cuenta que las discrepancias lógicas entre el tipo de retorno declarado en PostgreSQL y el tipo de datos de Python del objeto de retorno real no se marcan como error; el valor se convertirá en cualquier caso.

44.2.2. Null, None #

Si se pasa un valor SQL null a una función, el valor del argumento aparecerá como None en Python. Por ejemplo, la definición de la función pymax mostrada en Section 44.1 devolverá una respuesta incorrecta para entradas null. Podríamos añadir STRICT a la definición de la función para que PostgreSQL haga algo más razonable: si se pasa un valor null, la función no se llamará en absoluto, sino que simplemente devolverá un resultado null automáticamente. Alternativamente, podríamos comprobar si hay entradas null en el cuerpo de la función:

CREATE FUNCTION pymax (a integer, b integer)
  RETURNS integer
AS $$
  if (a is None) or (b is None):
    return None
  if a > b:
    return a
  return b
$$ LANGUAGE plpython3u;

Como se muestra arriba, para devolver un valor SQL null desde una función PL/Python, devuelve el valor None. Esto se puede hacer tanto si la función es estricta (strict) como si no.

44.2.3. Arrays, Lists #

Los valores de array de SQL se pasan a PL/Python como una lista de Python. Para devolver un valor de array de SQL desde una función PL/Python, devuelve una lista de Python:

CREATE FUNCTION return_arr()
  RETURNS int[]
AS $$
return [1, 2, 3, 4, 5]
$$ LANGUAGE plpython3u;

SELECT return_arr();
 return_arr
-------------
 {1,2,3,4,5}
(1 row)

Los arrays multidimensionales se pasan a PL/Python como listas anidadas de Python. Un array bidimensional es una lista de listas, por ejemplo. Al devolver un array SQL multidimensional desde una función PL/Python, las listas internas en cada nivel deben ser todas del mismo tamaño. Por ejemplo:

CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$
plpy.info(x, type(x))
return x
$$ LANGUAGE plpython3u;

SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]);
INFO:  ([[1, 2, 3], [4, 5, 6]], <type 'list'>)
 test_type_conversion_array_int4
---------------------------------
 {{1,2,3},{4,5,6}}
(1 row)

También se aceptan otras secuencias de Python, como las tuplas, para mantener la compatibilidad con las versiones 9.6 y anteriores de PostgreSQL, cuando no se admitían arrays multidimensionales. Sin embargo, siempre se tratan como arrays unidimensionales, porque son ambiguas con los tipos compuestos. Por la misma razón, cuando se utiliza un tipo compuesto en un array multidimensional, debe representarse mediante una tupla, en lugar de una lista.

Ten en cuenta que en Python, las cadenas son secuencias, lo que puede tener efectos no deseados que podrían resultar familiares a los programadores de Python:

CREATE FUNCTION return_str_arr()
  RETURNS varchar[]
AS $$
return "hello"
$$ LANGUAGE plpython3u;

SELECT return_str_arr();
 return_str_arr
----------------
 {h,e,l,l,o}
(1 row)

44.2.4. Tipos compuestos #

Los argumentos de tipo compuesto se pasan a la función como mapeos (mappings) de Python. Los nombres de los elementos del mapeo son los nombres de los atributos del tipo compuesto. Si un atributo en la fila pasada tiene el valor null, tiene el valor None en el mapeo. Aquí tienes un ejemplo:

CREATE TABLE employee (
  name text,
  salary integer,
  age integer
);

CREATE FUNCTION overpaid (e employee)
  RETURNS boolean
AS $$
  if e["salary"] > 200000:
    return True
  if (e["age"] < 30) and (e["salary"] > 100000):
    return True
  return False
$$ LANGUAGE plpython3u;

Hay múltiples formas de devolver filas o tipos compuestos desde una función de Python. Los siguientes ejemplos asumen que tenemos:

CREATE TYPE named_value AS (
  name   text,
  value  integer
);

Un resultado compuesto se puede devolver como:

Tipo secuencia (una tupla o lista, pero no un conjunto porque no es indexable)

Los objetos de secuencia devueltos deben tener el mismo número de elementos que campos tiene el tipo de resultado compuesto. El elemento con índice 0 se asigna al primer campo del tipo compuesto, el 1 al segundo, y así sucesivamente. Por ejemplo:

CREATE FUNCTION make_pair (name text, value integer)
  RETURNS named_value
AS $$
  return ( name, value )
  # o alternativamente, como lista: return [ name, value ]
$$ LANGUAGE plpython3u;

Para devolver un valor SQL null para cualquier columna, inserta None en la posición correspondiente.

Cuando se devuelve un array de tipos compuestos, no se puede devolver como una lista, porque es ambiguo si la lista de Python representa un tipo compuesto o otra dimensión de array.

Mapeo (diccionario)

El valor de cada columna del tipo de resultado se recupera del mapeo utilizando el nombre de la columna como clave. Ejemplo:

CREATE FUNCTION make_pair (name text, value integer)
  RETURNS named_value
AS $$
  return { "name": name, "value": value }
$$ LANGUAGE plpython3u;

Cualquier par clave/valor adicional en el diccionario es ignorado. Las claves faltantes se tratan como errores. Para devolver un valor SQL null para cualquier columna, inserta None con el nombre de la columna correspondiente como clave.

Objeto (cualquier objeto que proporcione el método __getattr__)

Esto funciona de la misma manera que un mapeo. Ejemplo:

CREATE FUNCTION make_pair (name text, value integer)
  RETURNS named_value
AS $$
  class named_value:
    def __init__ (self, n, v):
      self.name = n
      self.value = v
  return named_value(name, value)

  # o simplemente
  class nv: pass
  nv.name = name
  nv.value = value
  return nv
$$ LANGUAGE plpython3u;

También se admiten funciones con parámetros OUT. Por ejemplo:

CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
return (1, 2)
$$ LANGUAGE plpython3u;

SELECT * FROM multiout_simple();

Los parámetros de salida de los procedimientos se devuelven de la misma manera. Por ejemplo:

CREATE PROCEDURE python_triple(INOUT a integer, INOUT b integer) AS $$
return (a * 3, b * 3)
$$ LANGUAGE plpython3u;

CALL python_triple(5, 10);

44.2.5. Funciones que devuelven conjuntos (Set-Returning) #

Una función PL/Python también puede devolver conjuntos de tipos escalares o compuestos. Hay varias formas de lograr esto porque el objeto devuelto se convierte internamente en un iterador. Los siguientes ejemplos asumen que tenemos el tipo compuesto:

CREATE TYPE greeting AS (
  how text,
  who text
);

Un resultado de conjunto se puede devolver desde un:

Tipo secuencia (tupla, lista, conjunto)

CREATE FUNCTION greet (how text)
  RETURNS SETOF greeting
AS $$
  # devuelve una tupla que contiene listas como tipos compuestos
  # todas las demás combinaciones también funcionan
  return ( [ how, "World" ], [ how, "PostgreSQL" ], [ how, "PL/Python" ] )
$$ LANGUAGE plpython3u;

Iterador (cualquier objeto que proporcione los métodos __iter__ y __next__)

CREATE FUNCTION greet (how text)
  RETURNS SETOF greeting
AS $$
  class producer:
    def __init__ (self, how, who):
      self.how = how
      self.who = who
      self.ndx = -1

    def __iter__ (self):
      return self

    def __next__(self):
      self.ndx += 1
      if self.ndx == len(self.who):
        raise StopIteration
      return ( self.how, self.who[self.ndx] )

  return producer(how, [ "World", "PostgreSQL", "PL/Python" ])
$$ LANGUAGE plpython3u;

Generador (yield)

CREATE FUNCTION greet (how text)
  RETURNS SETOF greeting
AS $$
  for who in [ "World", "PostgreSQL", "PL/Python" ]:
    yield ( how, who )
$$ LANGUAGE plpython3u;

También se admiten funciones que devuelven conjuntos con parámetros OUT (usando RETURNS SETOF record). Por ejemplo:

CREATE FUNCTION multiout_simple_setof(n integer, OUT integer, OUT integer) RETURNS SETOF record AS $$
return [(1, 2)] * n
$$ LANGUAGE plpython3u;

SELECT * FROM multiout_simple_setof(3);