PL/Python:
Programando em Pyhon no PostgreSQL
Juliano Atanazio
About me
Juliano Atanazio
● Graduated in Computer Science for Business Management (Informática para Gestão de
Negócios), FATEC Zona Sul, São Paulo – SP;
● PostgreSQL DBA;
● Linux admin;
● Instructor (PostgreSQL);
● LPIC-1, LPIC-2 Certified;
● Linux user since 2000;
● Free Software enthusiast;
● Favorite technologies: PostgreSQL, Linux, Python, Shell Script, FreeBSD, etc...;
● Headbanger :) \m/
2/52
Sobre esta Apresentação
Esta apresentação é uma versão alternativa, em português de
"On snakes and elephants
Using Python inside PostgreSQL"
de
Jan Urbański
https://wulczer.org/pywaw-summit.pdf
3/52
Sobre Python
● Linguagem de altíssimo nível;
● Totalmente orientada a objetos;
● Fácil de aprender e de dar manutenção;
● Rica em bibliotecas;
● Propósitos gerais;
● Tipagem forte e dinânica;
● Comunidade forte e solícita;
● Objetiva e muito produtiva: faça mais escrevendo menos.
import this
www.python.org
4/52
Sobre PostgreSQL
É um sistema gerenciador de banco de dados objeto-relacional
originalmente desenvolvido na Universidade da Califórnia no
Departamento de Ciências da Computação em Berkeley.
Hoje é desenvolvido e mantido pelo PGDG (PostgreSQL Global
Development Group – Grupo de Desenvolvimento Global do
PostgreSQL).
● Open source;
● Rico em recursos avançados;
● Muito amigável para programadores;
● Extensível
www.postgresql.org
5/52
Stored Procedures no PostgreSQL
Em outros SGBDs há diferenças entre stored procedures e
functions, no PostgreSQL é tudo function;
Implementa operações que não seriam fáceis ou sequer seriam
possíveis com SQL;
Uma função é chamada dentro de SQL;
Pode encapsular lógica de negócio;
6/52
Stored Procedures no PostgreSQL
PL/pgSQL é a linguagem padrão para se escrever functions no
Postgres;
PL/Python, PL/Perl e PL/Tcl são nativamente suportadas, mas é
possível utilizar outras linguagens procedurais tais como PL/Java,
PL/PHP, PL/v8 (JavaScript), PL/R e etc;
Suporta sobrecarga de função.
7/52
Funções: Sintaxe e Exemplos
Criação da função fc_foo sem parâmetros:
> CREATE OR REPLACE FUNCTION fc_foo()
RETURNS VARCHAR AS $$
BEGIN
RETURN 'Hello, World!';
END; $$ LANGUAGE PLPGSQL;
8/52
Funções: Sintaxe e Exemplos
Criação da função fc_foo com parâmetros:
> CREATE OR REPLACE FUNCTION fc_foo(num1 INT, num2 INT)
RETURNS INT AS $$
BEGIN
RETURN (num1 + num2) * 2;
END; $$ LANGUAGE PLPGSQL;
9/52
Funções: Sintaxe e Exemplos
Execução da função fc_foo sem parâmetros:
> SELECT fc_foo();
fc_foo
---------------
Hello, World!
10/52
Funções: Sintaxe e Exemplos
Execução da função fc_foo com parâmetros:
> SELECT fc_foo(2, 5);
fc_foo
--------
14
11/52
Blocos Anônimos
Sintaxe:
DO $$
. . .
codigo
. . .
$$ LANGUAGE linguagem;
12/52
Blocos Anônimos
> SET client_min_messages = 'notice';
> DO $$
DECLARE n_con INT;
BEGIN
SELECT count(client_addr)
INTO n_con
FROM pg_stat_activity;
RAISE NOTICE
'Conexões não-locais: %', n_con;
END; $$ LANGUAGE PLPGSQL;
NOTICE: Conexões não-locais: 10
13/52
PL/Python
PL/Python é a implementação de Python como linguagem
procedural no Postgres;
Permite utilizar todas as bibliotecas de Python instaladas no
sistema operacional;
Ao rodar PL/Python pela primeira vez após o servidor
PostgreSQL inicializar, um interpretador Python é inicializado
dentro do processo backend;
https://www.postgresql.org/docs/current/static/plpython.html
14/52
PL/Python
Para evitar uma demora inicial pode-se carregar a biblioteca de
sistema plpython.so em alguma configuração “preload”;
Tipos de dados PostgreSQL são convertidos para os de Python e
vice-versa;
Os argumentos de uma função têm escopo global;
O módulo plpy fornece acesso ao banco e funções;
O “path” dos módulos depende da variável de ambiente
PYTHONPATH do usuário que inicia o serviço do PostgreSQL.
15/52
PL/Python: Tipos de Dados
PostgreSQL Python
bigint, integer, smallint int
Real, double float
numeric Decimal
boolean bool
text, varchar, char str
array list
Tipos personalizados dict
null None
16/52
PL/Python: O “U” da Questão
PL/Python é uma linguagem procedural considerada “untrusted”
(não confiável): o que implica na denominação PL/PYTHONU;
O que significa que não é oferecida qualquer forma de restrição
para usuários fazer;
Permite executar ações fora do banco de dados;
Somente superusers podem criar funções em linguagens como
PL/PYTHONU.
“Com grandes poderes vêm grandes responsabilidades”.
Tio Ben (Stan Lee)
17/52
PL/Python vs PL/pgSQL
Qual é a melhor linguagem procedural para PostgreSQL?
PL/Python ou PL/pgSQL?
Depende…
De forma geral podemos dizer que para operações puras na base
de dados é melhor utilizar PL/pgSQL.
E PL/Python para operações que vão além do banco, dando um
maior poder de fogo para o PostgreSQL.
Cada uma tem seu valor e sua aplicabilidade dentro de um
contexto :)
18/52
PL/Python vs PL/pgSQL
Variáveis Especiais para Triggers
PL/pgSQL PL/Python Tipo Descrição
Qual tipo de evento disparou o trigger.
TG_OP TD['event'] Possíveis valores: INSERT, UPDATE, DELETE, ou
TRUNCATE.
string Quando o gatilho (trigger) foi disparado.
TG_WHEN TD['when'] Possíveis valores: BEFORE, AFTER, ou INSTEAD
OF.
TG_LEVEL TD['level'] Qual o nível, por linha ou por comando.
Possíveis valores: ROW ou STATEMENT.
Variável que contém a nova linha para INSERT ou
NEW TD['new'] UPDATE em nível de linha (ROW level). É nula
para DELETE ou nível STATEMENT.
record
Para nível em linha (ROW level), contém a linha
OLD TD['old'] antiga para UPDATE ou DELETE. É nula para o
vível STATEMENT ou para INSERT.
TG_NAME TD['name'] name Nome do gatilho.
19/52
PL/Python vs PL/pgSQL
Variáveis Especiais para Triggers
PL/pgSQL PL/Python Tipo Descrição
TG_TABLE_NAME / Nome da tabela que disparou o
TG_RELNAME TD['table_name']
(depreciado) gatilho.
name
TG_TABLE_SCHEMA TD['table_schema'] Nome do esquema (schema)
da tabela que disparou o gatilho.
TG_RELID TD['relid'] oid OID da tabela que disparou o
gatilho.
Se o comando CREATE
TRIGGER inclui argumentos
para a função eles estão
array disponíveis de TD['args'][0] a
TG_ARGV[] TD['args'] De TD['args'][n - 1].
string Índices de array inválidos
(menor do que zero ou maior do
que TG_NARGS) resultam em
um valor nulo.
20/52
PL/Python vs PL/pgSQL
Variáveis Especiais para Triggers
PL/pgSQL PL/Python Tipo Descrição
TG_NARGS len(TD['args']) inteiro A quantidade de argumentos
passados à função do gatilho.
Se TD['when'] for BEFORE ou INSTEAD OF e TD['level']
for ROW, você pode retornar None ou “OK” da função Python
para indicar que a linha não foi modificada, “SKIP” para abortar o
evento de se TD['event'] for INSERT ou UPDATE você pode
retornar “MODIFY” para indicar que você modificou nova linha.
Caso contrário o valor de retorno será ignorado.
21/52
PL/Python: Instalação
Nosso Laboratório
SO: CentOS Linux 7.5
Python: 3.6
PostgreSQL: 10
Diretório para bibliotecas Python : /var/lib/pgsql/python
PYTHONPATH: ${PYTHONPATH}:/var/lib/pgsql/python
Obs.: Sinta-se à vontade para usar qualquer versão diferente.
Informações meramente ilustrativas que é conforme o laboratório de testes aqui.
22/52
PL/Python: Instalação
Antes da instalação do PostgreSQL indique para a variável de
ambiente o binário referente à versão de Python utilizada:
$ export PYTHON=`which python3.6`
É preciso ter também os pacotes com os headers em C de
Python (-dev, -devel).
No processo de configure utilize a opção --with-python:
$ ./configure --with-python
23/52
PL/Python: Instalação
Crie o diretório Python:
$ mkdir /var/lib/pgsql/python
No arquivo de perfil de shell do usuário
(.bashrc, .bashrc_profile, .profile) coloque a seguinte linha:
export PYTHONPATH="${PYTHONPATH}:/var/lib/pgsql/python"
24/52
PL/Python: Instalação
Para testar podemos iniciar o PostgreSQL passando o valor de
PYTHONPATH:
$ PYTHONPATH="${PYTHONPATH}:/var/lib/pgsql/python" \
pg_ctl start
Podemos incluir a variável PYTHONPATH no Unit File SystemD
do serviço do PostgreSQL:
# systemctl edit --full postgresql.service
25/52
PL/Python: Instalação
Na sessão Unit, diretiva Environment do Unit File:
[Unit]
. . .
Environment=PYTHONPATH=/var/lib/pgsql/python
Reinicialize o serviço do PostgreSQL:
# systemctl restart postgresql.service
26/52
PL/Python: Exemplos
Criação da base de dados de exemplo:
> CREATE DATABASE db_plpython;
Conectar à base:
> \c db_plpython
Habilitar PL/Python 3 na base de dados atual:
> CREATE EXTENSION plpython3u;
27/52
PL/Python: Exemplos
Uma simples função anônima:
> DO $$
from sys import path
plpy.notice(path)
$$ LANGUAGE PLPYTHON3U;
NOTICE: ['/var/lib/pgsql/python', '/usr/lib64/python36.zip',
'/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/
lib64/python3.6/site-packages', '/usr/lib/python3.6/site-
packages']
28/52
PL/Python: Exemplos
Função em PL/Python 3 sem argumentos:
> CREATE OR REPLACE FUNCTION fc_py()
RETURNS VARCHAR AS $$
return 'Hello, World!!!'
$$ LANGUAGE plpython3u;
29/52
PL/Python: Exemplos
Sobrecarga de função:
> CREATE OR REPLACE FUNCTION fc_py(num1 INT, num2 INT)
RETURNS INT AS $$
return num1 + num2
$$ LANGUAGE plpython3u;
30/52
PL/Python: Exemplos
Testando a primeira função criada:
> SELECT fc_py();
fc_py
-----------------
Hello, World!!!
Testando a segunda função criada:
> SELECT fc_py(2, 5);
fc_py
-------
7
31/52
PL/Python: Boas Práticas
● Mantenha seu código Python em um módulo;
● Faça todas suas funções SQL com apenas duas linhas;
● Teste o código Python fazendo mock [1].
[1] https://docs.python.org/3/library/unittest.mock.html
32/52
PL/Python: Boas Práticas
Criação do módulo teste dentro do diretório que está em
PYTHONPATH:
$ cat << EOF > /var/lib/pgsql/python/teste.py
def py_version():
import sys
return sys.version.split()[0]
EOF
Conexão ao banco via psql:
$ psql db_plpython
33/52
PL/Python: Boas Práticas
Criação da função seguindo as boas práticas:
> CREATE OR REPLACE FUNCTION fc_py_version()
RETURNS VARCHAR AS $$
from teste import py_version
return py_version()
$$ LANGUAGE plpython3u;
34/52
PL/Python: Boas Práticas
Teste da função:
> SELECT 'Minha versão de Python é: '|| fc_py_version()
AS "Versão de Python";
Versão de Python
---------------------------------
Minha versão de Python é: 3.6.3s
35/52
PL/Python: Use Case
Serão demonstrados 2 (dois) casos de uso
• Converter XML para JSON, na base de dados de um campo
do tipo xml para jsonb;
• Em um sistema de cadastro de apostilas PDF extrair o sumário
(TOC – Table Of Contents).
36/52
PL/Python: Use Case
Instalar os módulos que serão utilizados:
# python3 -m pip install xmltodict pdfminer.six
Editar o arquivo que será o módulo com funções personalizadas:
$ vim /var/lib/pgsql/python/MyFunctions.py
37/52
PL/Python: Use Case
/var/lib/pgsql/python/MyFunctions.py
import json
import pprint
import xmltodict
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfparser import PDFParser
Continua...
38/52
PL/Python: Use Case
/var/lib/pgsql/python/MyFunctions.py
def conv_xml_2_json(doc):
'Converte uma string XML para JSON'
doc = xmltodict.parse(doc)
return json.dumps(doc, ensure_ascii=False)
Continua...
39/52
PL/Python: Use Case
/var/lib/pgsql/python/MyFunctions.py
def pdf_extract_toc(f):
'Extrai o sumário de um PDF'
f = open(f, 'rb')
parser = PDFParser(f)
document = PDFDocument(parser)
outlines = document.get_outlines()
lista_tmp = []
for (level, title, dest, a, se) in outlines:
lista_tmp.append(title)
return '\n'.join(lista_tmp)
40/52
PL/Python: Use Case
Converter XML para JSON
No banco criar a função para converter de XML para JSON:
> CREATE OR REPLACE FUNCTION fc_py_xml2jsonb(doc TEXT)
RETURNS jsonb AS $$
from MyFunctions import conv_xml_2_json
return conv_xml_2_json(doc)
$$ LANGUAGE PLPYTHON3U;
41/52
PL/Python: Use Case
Criação da tabela:
> CREATE TABLE tb_xml(
id serial PRIMARY KEY,
campo_xml xml);
42/52
PL/Python: Use Case
Inserir o registro:
> INSERT INTO tb_xml (campo_xml) VALUES (
'<receita nome="pão" tempo_de_preparo="5 minutos"
tempo_de_cozimento="1 hora">
<titulo>Pão simples</titulo>
<ingredientes>
<ingrediente quantidade="3"
unidade="xícaras">Farinha</ingrediente>
<ingrediente quantidade="7"
unidade="gramas">Fermento</ingrediente>
<ingrediente quantidade="1.5" unidade="xícaras"
estado="morna">Água</ingrediente>
<ingrediente quantidade="1" unidade="colheres de
chá">Sal</ingrediente>
</ingredientes>
<instrucoes>
<passo>Misture todos os ingredientes, e dissolva bem.</passo>
<passo>Cubra com um pano e deixe por uma hora em um local
morno.</passo>
<passo>Misture novamente, coloque numa bandeja e asse num
forno.</passo>
</instrucoes>
</receita>');
43/52
PL/Python: Use Case
Converter para JSON:
> SELECT
jsonb_pretty(fc_py_xml2jsonb(campo_xml::text))
FROM tb_xml;
jsonb_pretty
------------------------------------------------------------------------------
{ +
"receita": { +
"@nome": "pão", +
"titulo": "Pão simples", +
"instrucoes": { +
"passo": [ +
"Misture todos os ingredientes, e dissolva bem.", +
"Cubra com um pano e deixe por uma hora em um local morno.",+
"Misture novamente, coloque numa bandeja e asse num forno." +
] +
}, +
. . .
44/52
PL/Python: Use Case
Extrair o Sumário de um PDF
Criação da função:
> CREATE OR REPLACE FUNCTION
fc_py_pdf_extract_toc (arquivo TEXT)
RETURNS TEXT AS $$
from MyFunctions import pdf_extract_toc
return pdf_extract_toc(arquivo)
$$ LANGUAGE PLPYTHON3U;
45/52
PL/Python: Use Case
Criação da tabela:
> CREATE TABLE tb_apostila(
id serial PRIMARY KEY,
titulo VARCHAR(200),
autor VARCHAR(70),
toc text);
46/52
PL/Python: Use Case
Inserir um registro:
> INSERT INTO tb_apostila (titulo, autor, toc) VALUES (
'PostgreSQL - SQL Básico',
'Juliano Atanazio',
fc_py_pdf_extract_toc('/tmp/postgresql_sql_basico.pdf'));
47/52
PL/Python: Use Case
Verificar o que foi inserido:
> SELECT id, titulo, autor, toc FROM tb_apostila;
id | titulo | autor | toc
----+-------------------------+------------------+-------------------------------------------------------------
1 | PostgreSQL - SQL Básico | Juliano Atanazio | 1 SQL +
| | | 1.1 O que é SQL +
| | | 1.2 Subdivisões SQL +
| | | 1.2.1 DDL +
| | | 1.2.2 DML +
| | | 1.2.3 DCL +
| | | 1.3 Identificadores +
| | | 1.4 Operadores +
| | | 1.5 Comentários SQL +
| | | 2 Tipos de Dados +
| | | 2.1 Sobre Tipos de Dados +
. . .
48/52
Conclusão
Utilize PL/Python para coisas
que PL/pgSQL não atende.
PL/Python por ser a
implementação de Python como
linguagem procedural no
PostgreSQL permite que se use
todo seu poder das “baterias
inclusas” além de pacotes de
bibliotecas extras instaladas via
pip, por exemplo.
49/52
Donate!
The elephant needs you!
Contribute! :)
http://www.postgresql.org/about/donate/
50/52
Save our planet!
51/52
See you soon!!!
:)
Juliano Atanazio
juliano777@gmail.com
http://slideshare.net/spjuliano
https://speakerdeck.com/julianometalsp
https://juliano777.wordpress.com
52/52