44.2. Valores dos dados #

44.2.1. Mapeamento de tipo de dados
44.2.2. Null, None
44.2.3. Matrizes, Listas
44.2.4. Tipos de dados compostos
44.2.5. Funções que retornam conjunto

De modo geral, o objetivo do PL/Python é fornecer um mapeamento natural entre os mundos do PostgreSQL e do Python. Isto orienta as regras de mapeamento de dados descritas abaixo.

44.2.1. Mapeamento de tipo de dados #

Quando uma função PL/Python é chamada, seus argumentos são convertidos de seu tipo de dados no PostgreSQL para o tipo de dados no Python correspondente:

  • O tipo de dados boolean do PostgreSQL é convertido para bool no Python.

  • Os tipos de dados smallint, int, bigint e oid do PostgreSQL são convertidos para int no Python.

  • Os tipos de dados real e double do PostgreSQL são convertidos para float no Python.

  • O tipo de dados numeric do PostgreSQL é convertido para Decimal no Python. Este tipo é importado do pacote cdecimal, se estiver disponível. Caso contrário, será usado decimal.Decimal da biblioteca padrão. cdecimal é muito mais rápido do que decimal. Entretanto, no Python 3.3 e superior, cdecimal foi integrado à biblioteca padrão sob o nome decimal, portanto, não há mais nenhuma diferença.

  • O tipo de dados bytea do PostgreSQL é convertido para bytes no Python.

  • Todos os outros tipos de dados, incluindo os tipos de dados de cadeia de caracteres do PostgreSQL, são convertidos para str no Python. (em Unicode, como todas as cadeias de caracteres em Python).

  • Para os tipos de dados não escalares, veja abaixo.

Quando uma função escrita em PL/Python retorna, seu valor retornado é convertido para o tipo de dados de retorno do PostgreSQL declarado na função, da seguinte forma:

  • Quando o tipo de dados do valor retornado pelo PostgreSQL for boolean, o valor retornado será avaliado como conforme as regras do Python. Ou seja, 0 e cadeia de caracteres vazia são considerado como sendo falso, mas notadamente o valor 'f' é considerado como sendo verdade.

  • Quando o tipo de dados retornado pelo PostgreSQL for bytea, o valor retornado será convertido para o tipo de dados bytes do Python usando as respectivas funções integradas do Python e, em seguida, o resultado será convertido de volta para bytea.

  • Para todos os outros tipos de dados retornados pelo PostgreSQL, o valor retornado é convertido em uma cadeia de caracteres usando a função integrada do Python str, e o resultado é passado para a função de entrada do tipo de dados PostgreSQL. (Se o valor no Python for do tipo de dados float, será convertido usando a função integrada repr em vez de str, para evitar perda de precisão.)

    As cadeias de caracteres são convertidas automaticamente para a codificação do servidor PostgreSQL quando são passadas para o PostgreSQL.

  • Para os tipos de dados não escalares, veja abaixo.

Note que as incompatibilidades lógicas entre o tipo de dados retornado declarado do PostgreSQL, e o tipo de dados retornado pelo Python, não são sinalizadas; o valor será convertido em todos os casos.

44.2.2. Null, None #

Se o valor nulo do SQL for passado para uma função, o valor do argumento aparecerá como None no Python. Por exemplo, a definição da função pymax mostrada em Funções em PL/Python retornará a resposta errada para entradas nulas. Poderia ser adicionado STRICT à definição da função para fazer com que o PostgreSQL faça algo mais razoável: se for passado um valor nulo, a função não será chamada, apenas retornando um resultado nulo automaticamente. Como alternativa, pode-se verificar se existem entradas nulas no corpo da função:

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 mostrado acima, para retornar o valor nulo do SQL por uma função escrita em PL/Python, deve ser retornado o valor None. Isto pode ser feito independentemente da função ser estrita ou não.

44.2.3. Matrizes, Listas #

Os valores do tipo de dados matriz do SQL são passados ​​para o PL/Python como uma lista do Python. Para retornar um valor do tipo de dados matriz do SQL por uma função escrita em PL/Python, deve ser retornada uma lista do Python:

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

SELECT retorna_matriz();

 retorna_matriz
----------------
 {1,2,3,4,5}
(1 linha)

As matrizes multidimensionais são passadas ​​para o PL/Python como listas Python aninhadas. Uma matriz bidimensional é uma lista de listas, por exemplo. Ao retornar uma matriz SQL multidimensional de uma função escrita em PL/Python, as listas internas em cada nível devem ser todas do mesmo tamanho. Por exemplo:

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

SELECT * FROM testa_conversao_tipo_matriz_int4(ARRAY[[1,2,3],[4,5,6]]);

INFO:  ([[1, 2, 3], [4, 5, 6]], <class 'list'>)
 testa_conversao_tipo_matriz_int4
----------------------------------
 {{1,2,3},{4,5,6}}
(1 linha)

Outras sequências do Python, como as tuplas, também são aceitas para manter a compatibilidade com as versões do PostgreSQL 9.6 e anteriores, quando as matrizes multidimensionais não tinham suporte. Entretanto, são sempre tratadas como matrizes unidimensionais, porque são ambíguas com os tipos de dados compostos. Pela mesma razão, quando um tipo de dados composto é usado em uma matriz multidimensional, ele deve ser representado por uma tupla, em vez de uma lista.

Note que, no Python, as cadeias de caracteres são sequências, podendo ter efeitos indesejáveis ​​que podem ser familiares aos programadores Python:

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

 retorna_cadeia_caracteres_matriz
----------------------------------
 {h,e,l,l,o}
(1 linha

44.2.4. Tipos de dados compostos #

Os argumentos do tipo de dados composto são passados ​​para a função como mapeamentos do Python. Os nomes dos elementos do mapeamento são os nomes dos atributos do tipo composto. Se um atributo na linha passada tiver o valor nulo, ele terá o valor None no mapeamento. A seguir está um exemplo:

CREATE TABLE empregado (
  nome text,
  salario integer,
  idade integer
);

CREATE FUNCTION sobressalario (e empregado)
  RETURNS boolean
AS $$
  if e["salario"] > 20000:
    return True
  if (e["idade"] < 30) and (e["salario"] > 10000):
    return True
  return False
$$ LANGUAGE plpython3u;

Existem várias maneiras de retornar tipos de dados linha ou composto de uma função Python. Os exemplos a seguir assumem que existe:

CREATE TYPE nome_valor AS (
  nome   text,
  valor  integer
);

Um resultado composto pode ser retornado como:

Tipo de dados de sequência (tupla ou lista, mas não um conjunto, porque não é indexável)

Os objetos de sequência retornados devem ter o mesmo número de itens que o tipo de dados do resultado composto tem de campos. O item com índice 0 é atribuído ao primeiro campo do tipo composto, 1 ao segundo e assim sucessivamente. Por exemplo:

CREATE FUNCTION fazer_par (nome text, valor integer)
  RETURNS nome_valor
AS $$
  return ( nome, valor )
  # ou de outra forma, como uma lista: return [ nome, valor ]
$$ LANGUAGE plpython3u;

Para retornar o valor nulo do SQL para qualquer coluna, deve ser colocado None na posição correspondente.

Quando é retornada uma matriz de tipos de dados compostos, ela não pode ser retornada como uma lista, porque é ambíguo se a lista Python representa um tipo de dados composto, ou outra dimensão da matriz.

Mapeamento (dicionário)

O valor para cada coluna do tipo de dados do resultado é recuperado do mapeamento com o nome da coluna como chave. Por exemplo:

CREATE FUNCTION fazer_par (nome text, valor integer)
  RETURNS nome_valor
AS $$
  return { "nome": nome, "valor": valor }
$$ LANGUAGE plpython3u;

Qualquer par extra de chave/valor do dicionário é ignorado. Chaves ausentes são tratadas como erros. Para retornar o valor nulo do SQL para qualquer coluna, deve ser escrito None com o nome da coluna correspondente como chave.

Objeto (qualquer objeto que forneça o método __getattr__)

Funciona da mesma forma que o mapeamento. Por exemplo:

CREATE FUNCTION fazer_par (nome text, valor integer)
  RETURNS nome_valor
AS $$
  class nome_valor:
    def __init__ (self, n, v):
      self.nome = n
      self.valor = v
  return nome_valor(nome, valor)

  # ou simplesmente
  class nv: pass
  nv.nome = nome
  nv.valor = valor
  return nv
$$ LANGUAGE plpython3u;

Funções com parâmetros OUT também têm suporte. Por exemplo:

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

SELECT * FROM multiout_simple();

Os parâmetros de saída dos procedimentos são passados ​​de volta da mesma maneira. Por exemplo:

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

CALL python_triplica(5, 10);

44.2.5. Funções que retornam conjunto #

As funções escritas em PL/Python também podem retornar conjunto, de tipo de dados escalar ou composto. Existem várias maneiras de se obter isto, porque o objeto retornado é internamente transformado em um iterador. Os exemplos a seguir assumem que existe o tipo de dados composto:

CREATE TYPE cumprimento AS (
  como text,
  quem text
);

Um conjunto de resultados pode ser retornado de:

Tipo de dados de sequência (tupla, lista, conjunto)

CREATE FUNCTION cumprimentar (como text)
  RETURNS SETOF cumprimento
AS $$
  # retornar uma tupla contendo as listas como tipos de dados compostos
  # todas as outras combinações também funcionam
  return ( [ como, "Mundo" ], [ como, "PostgreSQL" ], [ como, "PL/Python" ] )
$$ LANGUAGE plpython3u;

Iterador (qualquer objeto que forneça os métodos __iter__ e __next__)

CREATE FUNCTION cumprimentar (como text)
  RETURNS SETOF cumprimento
AS $$
  class produtor:
    def __init__ (self, como, quem):
      self.como = como
      self.quem = quem
      self.ndx = -1

    def __iter__ (self):
      return self

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

  return produtor(como, [ "Mundo", "PostgreSQL", "PL/Python" ])
$$ LANGUAGE plpython3u;

Gerador (yield)

CREATE FUNCTION cumprimentar (como text)
  RETURNS SETOF cumprimento
AS $$
  for quem in [ "Mundo", "PostgreSQL", "PL/Python" ]:
    yield ( como, quem )
$$ LANGUAGE plpython3u;

Funções que retornam conjunto com parâmetros OUT (usando RETURNS SETOF record) também têm suporte. Por exemplo:

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);