PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Visão geral da programação do lado do servidor no PostgreSQL


Existem várias maneiras de fazer com que o servidor Postgres execute código pré-definido. Abaixo está uma lista abrangente, com exemplos, de maneiras pelas quais você pode permitir que o servidor Postgres armazene lógica predefinida, que você pode usar posteriormente em seu aplicativo.

Funções SQL


O Postgres permite criar “funções definidas pelo usuário”, onde o corpo da função pode ser escrito em uma linguagem suportada. “Funções SQL” são funções definidas pelo usuário escritas em SQL regular, que é a maneira mais simples de encapsular consultas complexas e sequências de instruções SQL.

Aqui estão alguns exemplos:
-- update item price and record the change
CREATE FUNCTION update_price(item text, newprice numeric) RETURNS void AS $$
    UPDATE items SET price=$2 WHERE name=$1;
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', $2, now(), $1);
$$ LANGUAGE SQL;

-- a function from uuid-osp
CREATE FUNCTION uuid_timestamp_bits(uuid) RETURNS varbit AS
$$ SELECT ('x' || substr($1::text, 15, 4) || substr($1::text, 10, 4) ||
           substr($1::text, 1, 8) || substr($1::text, 20, 4))::bit(80)
          & x'0FFFFFFFFFFFFFFF3FFF' $$
LANGUAGE SQL STRICT IMMUTABLE;

As funções SQL podem aceitar e retornar tipos base, tipos compostos e linhas. Eles também suportam números variáveis ​​de argumentos, valores padrão para argumentos e argumentos polimórficos. Eles podem até retornar várias linhas, imitando um SELECT de uma tabela. Também não é necessário que eles devolvam nada.

No entanto, o corpo da função só pode conter instruções SQL. Isso significa que não há instruções de controle de fluxo (if, while, …), variáveis ​​e similares.

O comando CREATE FUNCTION é usado para criar a função. Como de costume, você pode ALTER e DROP-los.

Este é um ótimo lugar para começar a aprofundar:https://www.postgresql.org/docs/current/xfunc-sql.html

Funções C


Enquanto as funções SQL são as mais fáceis de escrever e menos poderosas, no outro extremo do espectro, as funções podem ser escritas em C e podem fazer praticamente qualquer coisa. Tais funções precisam ser codificadas em C e construídas como uma biblioteca compartilhada que pode ser carregada dinamicamente pelo Postgres.

Você precisa informar ao Postgres onde carregar a biblioteca compartilhada, o nome e a assinatura da função:
CREATE FUNCTION sum(integer, integer) RETURNS integer
    AS 'myfuncs', 'sum'
    LANGUAGE C STRICT;

Isso diz que a biblioteca compartilhada myfuncs.so , presente em um caminho de busca pré-definido, contém pontos de entrada que podem ser chamados pelo Postgres, sendo um dos pontos de entrada 'sum' que pode ser invocado como uma função.

O código real em C seria muito longo para incluir aqui, mas você pode ler tudo sobre ele na documentação. Combinado com a Interface de Programação do Servidor (SPI), é possível fazer quase qualquer operação que você pode fazer de qualquer outra maneira.

Por exemplo, com as funções C definidas aqui, você pode realizar solicitações HTTP:
SELECT status, content_type FROM http_get('https://postgresql.org/');

Também é possível escrever tais bibliotecas compartilhadas em outras linguagens como C++ ou Go, que podem construir bibliotecas compartilhadas com ligações “C”.

Funções PL/pgSQL


Além de SQL e C, você pode escrever funções em linguagens procedurais . Quatro dessas linguagens são suportadas pelo núcleo do PostgreSQL – pgSQL, Python, Perl e Tcl. O suporte para qualquer linguagem procedural vem de uma biblioteca compartilhada em C e opera como mod_perl ou mod_python da era Apache.

pgSQL é a linguagem canônica, mais usada, do tipo SQL, na qual as funções armazenadas do PostgreSQL são escritas. Está disponível por padrão, cortesia de ser instalado em template1 .

PL/pgSQL é uma linguagem completa com variáveis, expressões e comandos de controle; e inclui recursos como cursores para trabalhar com dados SQL em particular. Está amplamente documentado aqui.

Aqui está um exemplo:
CREATE FUNCTION repeat(times integer, s text)
    RETURNS text
    AS $$
DECLARE
    result text;
BEGIN
    result := '';
    FOR i IN 1..times LOOP
        result := result || s;
    END LOOP;
    RETURN result;
END;
$$
LANGUAGE plpgsql
IMMUTABLE;

-- psql> SELECT repeat(10, '*');
--    repeat
-- ------------
--  **********
-- (1 row)

Outras linguagens procedurais principais


As outras linguagens procedurais – Python, Perl, Tcl – permitem que os desenvolvedores usem uma linguagem com a qual já estejam confortáveis. Embora o suporte a essas linguagens resida na árvore de origem do Postgres, as distribuições geralmente não instalam os binários por padrão. Por exemplo, no Debian você pode ter que fazer:
sudo apt install postgresql-plpython-11

para instalar o suporte PL/Python para PostgreSQL 11.

Qualquer que seja a linguagem que você esteja usando para escrever uma função, o chamador não percebe nenhuma diferença em seu uso.

Python


A extensão PL/Python suporta escrever funções em Python 2 e Python 3. Para instalá-la, faça:
CREATE EXTENSION plpythonu;

Aqui está uma função escrita em PL/Python:
CREATE FUNCTION pymax (a integer, b integer)
  RETURNS integer
AS $$
  if a > b:
    return a
  return b
$$ LANGUAGE plpythonu;

O ambiente Python no qual o corpo da função é executado tem um módulo chamado plpy automaticamente importado para ele. Este módulo contém métodos que permitem preparar e executar consultas, manipular transações e trabalhar com cursores.

Mais informações podem ser encontradas no capítulo 46 da documentação do Postgres.

Perl


Bem, sim, Perl. Os processos de desenvolvimento, teste e construção do Postgres usam Perl extensivamente, e também é suportado como uma linguagem procedural. Para começar a usá-lo, certifique-se de que todos os pacotes binários relevantes para sua distro estejam instalados (exemplo “postgresql-plperl-nn” para Debian) e instale a extensão “plperl”.

Aqui está uma função escrita em PL/Perl:
CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    my ($x, $y) = @_;
    if (not defined $x) {
        return undef if not defined $y;
        return $y;
    }
    return $x if not defined $y;
    return $x if $x > $y;
    return $y;
$$ LANGUAGE plperl;

Documentação completa aqui.

Tcl


Tcl é mais um PL suportado pelo núcleo Postgres. Aqui está um exemplo:
CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS $$
    if {[argisnull 1]} {
        if {[argisnull 2]} { return_null }
        return $2
    }
    if {[argisnull 2]} { return $1 }
    if {$1 > $2} {return $1}
    return $2
$$ LANGUAGE pltcl;

Para mais informações, consulte os documentos aqui.

Linguagens de procedimento não essenciais


Além dessas linguagens, existem projetos de código aberto que desenvolvem e mantêm suporte para outras como Java, Lua, R etc.

Há uma lista aqui:https://www.postgresql.org/docs/current/external-pl.html

Funções agregadas


As funções agregadas operam sobre um conjunto de valores e retornam um único resultado. O PostgreSQL tem várias funções agregadas incorporadas (veja uma lista completa aqui). Por exemplo, para obter o desvio padrão populacional de todos os valores em uma coluna, você posso:
SELECT stddev_pop(grade) FROM students;

Você pode definir suas próprias funções agregadas que se comportam de maneira semelhante. Um agregado definido pelo usuário é montado a partir de algumas funções independentes individuais que funcionam no estado interno (por exemplo, o estado interno de um agregado que calcula a média pode ser as variáveis ​​"soma" e "contagem").

Aqui está um agregado definido pelo usuário que calcula a mediana de um conjunto de valores:
-- from https://wiki.postgresql.org/wiki/Aggregate_Median
CREATE OR REPLACE FUNCTION _final_median(NUMERIC[])
   RETURNS NUMERIC AS
$$
   SELECT AVG(val)
   FROM (
     SELECT val
     FROM unnest($1) val
     ORDER BY 1
     LIMIT  2 - MOD(array_upper($1, 1), 2)
     OFFSET CEIL(array_upper($1, 1) / 2.0) - 1
   ) sub;
$$
LANGUAGE 'sql' IMMUTABLE;
 
CREATE AGGREGATE median(NUMERIC) (
  SFUNC=array_append,
  STYPE=NUMERIC[],
  FINALFUNC=_final_median,
  INITCOND='{}'
);

que pode ser invocado como:
SELECT median(grade) FROM students;

Para obter mais informações, consulte os documentos sobre agregações e a instrução CREATE AGGREGATE.

Tipos definidos pelo usuário


As bibliotecas compartilhadas escritas em C que vimos anteriormente podem não apenas definir funções, mas também tipos de dados. Esses tipos definidos pelo usuário podem ser usados ​​como tipos de dados para colunas, assim como os tipos internos. Você pode definir funções para trabalhar com os valores de seus tipos definidos pelo usuário.

É preciso um pouco de código para definir um novo tipo. Veja os documentos aqui que o orientam na criação de um novo tipo para representar números complexos. A fonte Postgres também contém um código tutorial para isso.

Operadores


Os operadores tornam as funções mais fáceis de usar (por exemplo, escrever 1 + 2 em vez de sum(1, 2) ), e você pode definir operadores para tipos definidos pelo usuário usando a instrução CREATE OPERATOR.

Aqui está um exemplo para criar um + operador que mapeia para a funçãocomplex_add que adiciona dois complex números:
CREATE OPERATOR + (
    leftarg = complex,
    rightarg = complex,
    function = complex_add,
    commutator = +
);

Mais informações aqui e aqui.

Classes de operadores e famílias de operadores


As classes de operador permitem que seu tipo de dados funcione com o B-Tree integrado e outros métodos de indexação. Por exemplo, se você deseja criar um índice B-Tree em uma coluna do tipo “complexo”, você precisará informar ao Postgres como comparar dois valores desse tipo para determinar se um é menor, igual ou maior que o outro.

Seria bom que tipos complexos fossem comparados com inteiros ou valores de ponto flutuante, que é onde entram as famílias de operadores.

Você pode ler tudo sobre classes e famílias de operadores aqui.

Acionadores


Os gatilhos são um mecanismo poderoso para criar efeitos colaterais para operações normais, embora possam ser perigosos se usados ​​em excesso ou abusados. Essencialmente, os gatilhos conectam eventos a funções. A função referida pode ser invocada:
  • antes ou depois da inserção/atualização/exclusão de uma linha de uma tabela
  • no truncar de uma tabela
  • em vez de inserir/atualizar/excluir uma linha de uma visualização

A função pode ser invocada para cada linha afetada por uma declaração ou uma vez por declaração. E há ainda mais coisas, como cascata de gatilhos, todas explicadas aqui.

As funções de gatilho podem ser escritas em C ou em qualquer uma das funções PL, mas não em SQL. Aqui está um exemplo para inserir uma linha em uma auditoria tabela, para cada atualização feita no preço de um item .
-- first create the function
CREATE FUNCTION log_update() RETURNS TRIGGER AS $$
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', NEW.price, now(), OLD.item);
$$
LANGUAGE plpgsql;

-- then create the trigger
CREATE TRIGGER audit_price_changes
    AFTER UPDATE ON items
    FOR EACH ROW
    WHEN (OLD.price IS DISTINCT FROM NEW.price)
    EXECUTE FUNCTION log_update();

Leia tudo sobre gatilhos aqui, junto com a documentação CREATE TRIGGER.

Acionadores de eventos


Enquanto aciona responder a eventos DML em uma única tabela, gatilhos de eventos pode responder a eventos DDL em um banco de dados específico. Os eventos incluem criar, alterar, descartar uma variedade de objetos, como tabelas, índices, esquemas, visualizações, funções, tipos, operadores etc.

Aqui está um gatilho de evento que impede a queda de objetos do esquema de 'auditoria':
-- create function first
CREATE FUNCTION nodrop() RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
    IF EXISTS(
      SELECT 1
      FROM pg_event_trigger_dropped_objects() AS T
      WHERE T.schema_name = 'audit')
    THEN
      RAISE EXCEPTION 'not allowed to drop objects in audit schema';
    END IF;
END $$;

-- create event trigger
CREATE EVENT TRIGGER trigger_nodrop
    ON sql_drop
    EXECUTE FUNCTION nodrop();

Mais informações podem ser encontradas aqui e na documentação CREATE EVENT TRIGGER.

Regras


O PostgreSQL vem com um recurso que permite reescrever consultas antes de chegar ao planejador de consultas. A operação é um pouco semelhante a configurar o Nginx ou o Apache para reescrever uma URL recebida antes de processá-la.

Aqui estão dois exemplos que afetam as instruções INSERT em uma tabela e as fazem fazer outra coisa:
-- make inserts into "items" table a no-op
CREATE RULE rule1 AS ON INSERT TO items DO INSTEAD NOTHING;

-- make inserts go elsewhere
CREATE RULE rule2 AS ON INSERT TO items DO INSTEAD
    INSERT INTO items_pending_review VALUES (NEW.name, NEW.price);

Este capítulo da documentação tem mais informações sobre regras.

Procedimentos armazenados


A partir do Postgres 11, é possível criar procedures armazenados também. Comparado com funções armazenadas, há apenas uma coisa extra que os procedimentos podem fazer – controle de transações.

Aqui está um exemplo:
CREATE PROCEDURE check_commit(v integer)
LANGUAGE plpgsql AS $$
BEGIN
    IF v % 2 = 0 THEN
        COMMIT;
    ELSE
        ROLLBACK;
    END IF;
END $$;

-- call it
CALL check_commit(10);

Veja aqui e aqui para mais informações.

Outras coisas exóticas

Envolvedores de dados estrangeiros


Os Foreign Data Wrappers (FDWs) permitem que você converse com outras fontes de dados, como outro servidor Postgres, MySQL, Oracle, Cassandra e muito mais. Toda a lógica de acesso ao servidor externo é escrita em C, como uma biblioteca compartilhada.

Existe até um armazenamento colunar chamado cstore_fdw baseado em FDW.

Você pode encontrar uma lista de implementação do FDW no Postgres Wiki e mais documentação aqui.

Métodos de acesso ao índice


O PostgreSQL vem com tipos de índice como B-Tree, hash, GIN e muito mais. É possível escrever seu próprio tipo de índice semelhante a este, como uma biblioteca compartilhada em C. Mais detalhes aqui.

Métodos de acesso à tabela


Com o próximo PostgreSQL 12, será possível criar sua própria estrutura de armazenamento de dados. Ao implementar a interface descrita aqui, você pode armazenar os dados da tupla fisicamente no disco da maneira que desejar.

Plugins de replicação lógica


No PostgreSQL, a replicação lógica é implementada por uma “decodificação” do conteúdo do log write-ahead (WAL) em um formato arbitrário (como texto SQL ou json) e publicado aos assinantes em slots de replicação. Esta decodificação é feita viaplugin de saída de decodificação lógica , que pode ser implementado como uma biblioteca compartilhada em C, conforme descrito aqui. A biblioteca compartilhada “test_decoding” é um desses plugins, e você pode construir o seu próprio.

Gerenciador de linguagem de procedimento


Você também pode adicionar suporte para sua linguagem de programação favorita como Postgres PL criando um manipulador – novamente como uma biblioteca compartilhada C. Comece aqui para criar PL/Go ou PL/Rust!

Extensões


Extensões são a forma de gerenciamento de pacotes do Postgres. Digamos que você tenha uma função C que faz algo útil e algumas instruções SQL que fazem as instruções “CREATE FUNCTION” necessárias para configurá-la. Você pode agrupá-los como uma “extensão” que o Postgres pode instalar (e desinstalar) em uma única etapa (chamando “CREATE EXTENSION”). Ao lançar uma nova versão, você também pode incluir etapas de atualização na extensão.

Embora não seja a programação do lado do servidor em si, as extensões são a maneira padrão e muito eficiente de empacotar e distribuir seu código do lado do servidor.

Mais informações sobre extensões podem ser encontradas aqui e aqui.