44.6. Acesso ao banco de dados #

44.6.1. Funções de acesso ao banco de dados
44.6.2. Captura de erros

O módulo de linguagem PL/Python importa automaticamente um módulo Python chamado plpy. As funções e constantes neste módulo estão disponíveis no código Python como plpy.xxx.

44.6.1. Funções de acesso ao banco de dados #

O módulo plpy fornece várias funções para executar comandos de banco de dados:

plpy.execute(consulta [, limite])

Chamar plpy.execute com uma cadeia de caracteres de consulta, e um argumento opcional de limite de linhas, faz com que esta consulta seja executada, e o resultado seja retornado em um objeto de resultado.

Se o limite especificado for maior que zero, então plpy.execute irá trazer no máximo limite linhas, como se a consulta incluísse uma cláusula LIMIT. Omitir o limite ou especificá-lo como zero resulta em nenhum limite de linhas.

O objeto de resultado emula uma lista ou objeto de dicionário. O objeto de resultado pode ser acessado pelo número da linha e nome da coluna. Por exemplo:

res = plpy.execute("SELECT * FROM minha_tabela", 5)

retorna até 5 linhas de minha_tabela. Se minha_tabela tiver a coluna minha_coluna, esta coluna poderá ser acessada via:

foo = res[i]["minha_coluna"]

O número de linhas retornadas pode ser obtido usando a função integrada len.

O objeto de resultado possui estes métodos adicionais:

nrows()

Retorna o número de linhas processadas pelo comando. Note que não é necessariamente igual ao número de linhas retornadas. Por exemplo, o comando UPDATE irá definir este valor, mas não retornará nenhuma linha (a menos que seja usado RETURNING).

status()

O valor retornado por SPI_execute().

colnames()
coltypes()
coltypmods()

Retorna uma lista com os nomes das colunas, uma lista com os OIDs dos tipos de dados das colunas e uma lista com os modificadores específicos para os tipos de dados das colunas, respectivamente.

Estes métodos lançam uma exceção quando chamados em um objeto de resultado de um comando que não produziu um conjunto de resultados, por exemplo, UPDATE sem RETURNING, ou DROP TABLE. Mas não há problema em usar estes métodos em um conjunto de resultados contendo zero linhas.

__str__()

O método padrão __str__ é definido para ser possível, por exemplo, depurar os resultados da execução da consulta usando plpy.debug(res).

O objeto de resultado pode ser modificado.

Note que chamar plpy.execute fará com que todo o conjunto de resultados seja lido na memória. Esta função deve ser usada apenas quando se tiver certeza de que o conjunto de resultados será relativamente pequeno. Se não se desejar correr o risco do uso excessivo de memória ao obter resultados grandes, deve ser usado plpy.cursor em vez de plpy.execute.

plpy.prepare(consulta [, tipos_de_dados_dos_argumentos])
plpy.execute(plano [, argumentos [, limite]])

A instrução plpy.prepare prepara o plano de execução para a consulta. É chamada com uma cadeia de caracteres de consulta, e uma lista contendo os tipos de dados dos parâmetros, se houver referências a parâmetros na consulta. Por exemplo:

plano = plpy.prepare("SELECT sobrenome FROM usuarios WHERE nome = $1", ["text"])

onde text é o tipo de dados da variável a ser passada para $1. O segundo argumento é opcional se não se quiser passar nenhum parâmetro para a consulta.

Após preparar a instrução, é usada a variante da função plpy.execute para executá-la:

res = plpy.execute(plano, [nome], 5)

O plano é passado como primeiro argumento (em vez da cadeia de caracteres de consulta), e uma lista de valores para substituir na consulta como segundo argumento. O segundo argumento é opcional, se a consulta não tiver nenhum parâmetro. O terceiro argumento é o limite de linhas opcional, como antes.

Como alternativa, pode-se chamar o método execute no objeto do plano:

rv = plan.execute(["nome"], 5)

Os parâmetros da consulta, e os campos das linhas de resultado, são convertidos entre os tipos de dados do PostgreSQL e do Python, conforme descrito em Valores dos dados.

Quando se prepara um plano usando o módulo PL/Python, ele é salvo automaticamente. Leia a documentação do SPI (Interface de programação servidor) para obter uma descrição do que isto significa. Para fazer uso efetivo dessa funcionalidade nas chamadas de função, é necessário usar um dos dicionários de armazenamento persistente SD ou GD (veja Compartilhamento de dados). Por exemplo:

CREATE FUNCTION usesavedplan()
  RETURNS trigger
AS $$
    if "plan" in SD:
        plan = SD["plan"]
    else:
        plan = plpy.prepare("SELECT 1")
        SD["plan"] = plan
    # rest of function
$$ LANGUAGE plpython3u;

plpy.cursor(consulta)
plpy.cursor(plano [, argumentos])

A função plpy.cursor recebe os mesmos argumentos que plpy.execute (exceto pelo limite de linhas), e retorna um objeto de cursor, que permite processar grandes conjuntos de resultados em blocos menores. Assim como acontece com plpy.execute, pode ser usada uma cadeia de caracteres de consulta, ou um objeto de plano, junto com uma lista de argumentos, ou a função cursor pode ser chamada como um método do objeto de plano.

O objeto de cursor fornece o método fetch, que recebe como parâmetro um número inteiro, e retorna um objeto de resultado. A cada vez que é chamado o método fetch, o objeto retornado conterá o próximo lote de linhas, nunca maior que o valor do parâmetro. Assim que todas as linhas tiverem sido buscadas, o método fetch começará a retornar um objeto de resultado vazio. Os objetos de cursor também fornecem uma interface de iterador que busca uma linha de cada vez, até que todas as linhas tenham sido buscadas. Os dados buscados dessa forma não são retornados como objetos de resultado, mas sim como dicionários, com cada dicionário correspondendo a uma única linha de resultado.

Um exemplo de duas maneiras de processar dados de uma tabela grande é:

CREATE FUNCTION count_odd_iterator()
  RETURNS integer
AS $$
odd = 0
for row in plpy.cursor("select num from largetable"):
    if row['num'] % 2:
         odd += 1
return odd
$$ LANGUAGE plpython3u;

CREATE FUNCTION count_odd_fetch(batch_size integer)
  RETURNS integer
AS $$
odd = 0
cursor = plpy.cursor("select num from largetable")
while True:
    rows = cursor.fetch(batch_size)
    if not rows:
        break
    for row in rows:
        if row['num'] % 2:
            odd += 1
return odd
$$ LANGUAGE plpython3u;

CREATE FUNCTION count_odd_prepared()
  RETURNS integer
AS $$
odd = 0
plan = plpy.prepare("select num from largetable where num % $1 <> 0", ["integer"])
rows = list(plpy.cursor(plan, [2]))  # or: = list(plan.cursor([2]))

return len(rows)
$$ LANGUAGE plpython3u;

Os cursores são descartados automaticamente. Mas se for desejado liberar explicitamente todos os recursos mantidos por um cursor, deverá ser usado o método close. Uma vez fechado, o cursor não poderá mais ser buscado.

Dica

Não devem ser confundidos os objetos criados por plpy.cursor, com os objetos da DB-API definidos pelo PEP 249. Eles não possuem nada em comum, exceto o nome.

44.6.2. Captura de erros #

As funções que acessam o banco de dados podem encontrar erros, o que fará com que sejam interrompidas e levantada uma exceção. Tanto plpy.execute quanto plpy.prepare podem levantar uma instância de uma subclasse de plpy.SPIError, que, por padrão, vai interromper a função. Este erro pode ser tratado como qualquer outra exceção do Python, usando a construção try/except. Por exemplo:

CREATE FUNCTION try_adding_joe()
  RETURNS text
AS $$
    try:
        plpy.execute("INSERT INTO users(username) VALUES ('joe')")
    except plpy.SPIError:
        return "algo deu errado"
    else:
        return "Joe adicionado"
$$ LANGUAGE plpython3u;

A classe real da exceção lançada corresponde à condição específica que causou o erro. Veja a Tabela de códigos de erro do PostgreSQL para obter uma lista das possíveis condições. O módulo plpy.spieexceptions define uma classe de exceção para cada condição do PostgreSQL, derivando seus nomes do nome da condição. Por exemplo, division_by_zero se torna DivisionByZero, unique_violation se torna UniqueViolation, fdw_error se torna FdwError, e assim por diante. Cada uma dessas classes de exceção herda de SPIError. Esta separação facilita o tratamento de erros específicos. Por exemplo:

CREATE FUNCTION insert_fraction(numerator int, denominator int)
  RETURNS text
AS $$
from plpy import spiexceptions
try:
    plan = plpy.prepare("INSERT INTO fractions (frac) VALUES ($1 / $2)", ["int", "int"])
    plpy.execute(plan, [numerator, denominator])
except spiexceptions.DivisionByZero:
    return "o denominador não pode ser igual a zero."
except spiexceptions.UniqueViolation:
    return "já existe essa fração"
except plpy.SPIError as e:
    return "outro erro, SQLSTATE %s" % e.sqlstate
else:
    return "fraction inserted"
$$ LANGUAGE plpython3u;

Note que, como todas as exceções do módulo plpy.spieexceptions herdam de SPIError, uma cláusula except para tratá-lo detectará qualquer erro de acesso ao banco de dados.

Como uma forma alternativa para lidar com diferentes condições de erro, pode-se capturar a exceção SPIError, e determinar a condição de erro específica dentro do bloco except, observando o atributo sqlstate do objeto de exceção. Este atributo é um valor cadeia de caracteres, contendo o código de erro SQLSTATE. Esta abordagem fornece aproximadamente a mesma funcionalidade.