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.
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.
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.
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
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:
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.
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.
__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);
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:
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;
__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;
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);