41.6. Estruturas de controle #

41.6.1. Retorno de função
41.6.2. Retorno de procedimento
41.6.3. Chamada de procedimento
41.6.4. Controles de condição
41.6.5. Laços simples
41.6.6. Percorrendo os resultados da consulta
41.6.7. Percorrendo matrizes
41.6.8. Captura de erros
41.6.9. Obtenção de informações sobre o local da execução

As estruturas de controle são provavelmente a parte mais útil (e importante) do PL/pgSQL. Com as estruturas de controle do PL/pgSQL, é possível manipular os dados do PostgreSQL de uma maneira muito flexível e poderosa.

41.6.1. Retorno de função #

Existem dois comandos disponíveis que permitem retornar dados de uma função: RETURN e RETURN NEXT.

41.6.1.1. RETURN #

RETURN expressão;

RETURN com uma expressão termina a função e retorna o valor da expressão para quem efetuou a chamada. Esta forma é usada em funções PL/pgSQL que não retornam um conjunto.

Em uma função que retorna um tipo de dados escalar, o resultado da expressão será automaticamente convertido no tipo de dados retornado pela função, conforme descrito para atribuições. Mas, para retornar um valor composto (linha), deve ser escrita uma expressão que forneça exatamente o conjunto de colunas especificado, podendo exigir o uso de conversão explícita.

Se a função for declarada com parâmetros de saída, deve ser escrito apenas RETURN sem a expressão. Serão retornados os valores correntes das variáveis dos parâmetros de saída.

Se a função for declarada retornando void pode ser usada uma instrução RETURN para sair da função antecipadamente; mas não deve ser escrita uma expressão após o RETURN.

O valor retornado pela função não pode ser deixado indefinido. Se o controle atingir o final do bloco de nível superior da função sem atingir uma instrução RETURN, ocorrerá um erro de tempo de execução. No entanto, esta restrição não se aplica a funções com parâmetros de saída e funções que retornam void. Nesses casos, uma instrução RETURN é executada automaticamente se o bloco de nível superior terminar.

Alguns exemplos:

-- funções retornando tipo de dados escalar
RETURN 1 + 2;
RETURN variável_escalar;

-- funções retornando tipo de dados composto
RETURN variável_tipo_composto;
RETURN (1, 2, 'três'::text);  -- as colunas devem ser convertidas para os tipos corretos

41.6.1.2. RETURN NEXT e RETURN QUERY #

RETURN NEXT expressão;
RETURN QUERY consulta;
RETURN QUERY EXECUTE cadeia_de_caracteres_de_comando [ USING expressão [, ... ] ];

Quando a função PL/pgSQL é declarada retornando SETOF algumtipo, o procedimento a seguir é um pouco diferente. Neste caso, os itens individuais a serem retornados são especificados por uma sequência de comandos RETURN NEXT ou RETURN QUERY e, em seguida, um comando final RETURN sem nenhum argumento é usado para indicar que a função terminou sua execução. RETURN NEXT pode ser usado com tipos de dados escalares e compostos; com um resultado de tipo composto, será retornada uma tabela inteira de resultados. RETURN QUERY anexa os resultados da execução de uma consulta ao conjunto de resultados da função. RETURN NEXT e RETURN QUERY podem ser livremente misturados na mesma função retornando conjunto, caso em que seus resultados são concatenados.

RETURN NEXT e RETURN QUERY, na verdade, não causam o retorno da função — apenas anexam zero ou mais linhas ao conjunto de resultados da função. A execução então prossegue para a próxima instrução da função PL/pgSQL. À medida que sucessivos comandos RETURN NEXT ou RETURN QUERY são executados, o conjunto de resultados é construído. Um RETURN final, que não deve ter nenhum argumento, faz com que o controle saia da função (ou basta apenas deixar o controle chegar ao final da função).

RETURN QUERY possui a variante RETURN QUERY EXECUTE, que especifica a consulta a ser executada dinamicamente. Podem ser inseridas expressões de parâmetro na cadeia de caracteres da consulta computada por meio de USING, da mesma forma que no comando EXECUTE.

Se a função for declarada com parâmetros de saída, deve ser escrito apenas RETURN NEXT, sem expressão. A cada execução, os valores correntes da(s) variável(is) de parâmetro de saída são salvos para retornar no final como uma linha de resultado. Note que se deve declarar a função como retornando SETOF record quando houver vários parâmetros de saída, ou SETOF algumtipo quando houver apenas um parâmetro de saída do tipo algumtipo, para criar uma função que retorna conjunto com parâmetros de saída.

A seguir está um exemplo de função usando RETURN NEXT:

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'três');
INSERT INTO foo VALUES (4, 5, 'seis');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$CORPO$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- pode ser feito algum processamento aqui
        RETURN NEXT r; -- retornar a linha corrente do SELECT
    END LOOP;
    RETURN;
END;
$CORPO$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

 fooid | foosubid | fooname
-------+----------+---------
     1 |        2 | três
     4 |        5 | seis
(2 linhas)

A seguir é mostrado o exemplo de uma função usando RETURN QUERY:

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$CORPO$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- Como a execução da função ainda não terminou, pode-se verificar
    -- se foram retornadas linhas e, caso contrário, lançar uma exceção.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Nenhum voo em %.', $1;
    END IF;

    RETURN;
 END;
$CORPO$
LANGUAGE plpgsql;

-- Retorna os voos disponíveis, ou lança uma
-- exceção caso não haja voos disponíveis.
SELECT * FROM get_available_flightid(CURRENT_DATE);

Nota

A implementação corrente de RETURN NEXT e RETURN QUERY armazena todo o conjunto de resultados antes de retornar da função, conforme discutido acima, significando que, se a função PL/pgSQL produzir um conjunto de resultados muito grande, o desempenho poderá ser ruim: os dados serão escritos em disco para evitar o esgotamento da memória, mas a função em si não retornará até que todo o conjunto de resultados tenha sido gerado. Uma versão futura do PL/pgSQL poderá permitir que os usuários definam funções de retorno de conjunto que não tenham esta limitação. No momento, o ponto em que os dados começam a ser escritos em disco é controlado pelo parâmetro de configuração work_mem. Os administradores que possuem máquinas com memória suficiente para armazenar conjuntos de resultados maiores na memória, devem considerar o aumento desse parâmetro.

41.6.2. Retorno de procedimento #

Um procedimento não tem valor retornado. Um procedimento pode, portanto, terminar sem uma instrução RETURN. Se for desejado usar uma instrução RETURN para sair do código antecipadamente, deve ser escrito apenas RETURN, sem a expressão.

Se o procedimento tiver parâmetros de saída, os valores finais das variáveis de parâmetro de saída serão retornados a quem efetuou a chamada.

41.6.3. Chamada de procedimento #

Uma função, procedimento ou bloco DO escrito em PL/pgSQL pode chamar um procedimento usando a instrução CALL. Os parâmetros de saída são tratados de forma diferente de como funcionam na instrução CALL do SQL puro. Cada parâmetro OUT ou INOUT do procedimento deve corresponder a uma variável na instrução CALL, e ao retornar o procedimento atribui a esta variável seu valor de retorno. Por exemplo:

CREATE PROCEDURE triplicar(INOUT x int) AS
$CORPO$
BEGIN
    x := x * 3;
END;
$CORPO$
LANGUAGE plpgsql;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triplicar(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- mostra 15
END;
$$;

A variável correspondente a um parâmetro de saída pode ser uma variável simples, ou um campo de uma variável de tipo composto. No momento, não pode ser um elemento de uma matriz.

41.6.4. Controles de condição #

As instruções IF e CASE permitem executar comandos segundo determinadas condições. O PL/pgSQL possui três formas de IF:

  • IF ... THEN ... END IF

  • IF ... THEN ... ELSE ... END IF

  • IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF

e duas formas de CASE:

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

41.6.4.1. IF-THEN #

IF expressão_booleana THEN
    instruções
END IF;

As instruções IF-THEN são a forma mais simples de IF. As instruções entre THEN e END IF são executadas se a condição resultar em verdade. Caso contrário, são ignoradas.

Exemplo:

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

41.6.4.2. IF-THEN-ELSE #

IF expressão_booleana THEN
    instruções
ELSE
    instruções
END IF;

As instruções IF-THEN-ELSE são uma adição ao IF-THEN, permitindo especificar um conjunto alternativo de instruções que devem ser executadas se a condição não resultar em verdade. (Note que isto inclui o caso em que a condição é avaliada como nula.)

Exemplos:

IF parentid IS NULL OR parentid = ''
THEN
    RETURN fullname;
ELSE
    RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;

IF v_count > 0 THEN
    INSERT INTO users_count (count) VALUES (v_count);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

41.6.4.3. IF-THEN-ELSIF #

IF expressão_booleana THEN
    instruções
[ ELSIF expressão_booleana THEN
    instruções
[ ELSIF expressão_booleana THEN
    instruções
    ...
]
]
[ ELSE
    instruções ]
END IF;

Às vezes, há mais do que apenas duas alternativas. IF-THEN-ELSIF fornece um método conveniente para verificar várias alternativas sucessivamente. As condições IF são testadas sucessivamente, até que a primeira que resultar em verdade seja encontrada. Em seguida, a(s) instrução(ões) associada(s) são executadas, após o que o controle passa para a próxima instrução após o END IF. (Quaisquer condições IF subsequentes não são testadas.) Se nenhuma das condições IF resultar em verdade, então será executado o bloco ELSE (se houver).

Aqui está um exemplo:

IF number = 0 THEN
    result := 'zero';
ELSIF number > 0 THEN
    result := 'positivo';
ELSIF number < 0 THEN
    result := 'negativo';
ELSE
    -- hmm, a única outra possibilidade é que o número seja nulo
    result := 'NULL';
END IF;

A palavra-chave ELSIF também pode ser escrita como ELSEIF.

Uma maneira alternativa de realizar a mesma tarefa é aninhar instruções IF-THEN-ELSE, como no exemplo a seguir:

IF demo_row.sex = 'm' THEN
    pretty_sex := 'man';
ELSE
    IF demo_row.sex = 'f' THEN
        pretty_sex := 'woman';
    END IF;
END IF;

Entretanto, este método requer que seja escrito um END IF correspondente para cada IF, sendo muito mais complicado do que usar ELSIF quando existem várias alternativas.

41.6.4.4. CASE simples #

CASE expressão_de_procura
    WHEN expressão [, expressão [ ... ]] THEN
      instruções
  [ WHEN expressão [, expressão [ ... ]] THEN
      instruções
    ... ]
  [ ELSE
      instruções ]
END CASE;

A forma simples de CASE permite a execução condicional segundo a igualdade de operandos. A expressão_de_procura é avaliada (uma vez), e comparada sucessivamente a cada expressão nas cláusulas WHEN. Se for encontrada uma correspondência, então as instruções correspondentes serão executadas, e o controle passará para a próxima instrução após o END CASE. (As expressões WHEN subsequentes não são avaliadas.) Se nenhuma correspondência for encontrada, as instruções do ELSE serão executadas; mas se o ELSE não estiver presente, então será lançada a exceção CASE_NOT_FOUND.

Aqui está um exemplo simples:

CASE x
    WHEN 1, 2 THEN
        msg := 'um ou dois';
    ELSE
        msg := 'diferente de um e dois';
END CASE;

41.6.4.5. CASE com pesquisa #

CASE
    WHEN expressão_booleana THEN
      instruções
  [ WHEN expressão_booleana THEN
      instruções
    ... ]
  [ ELSE
      instruções ]
END CASE;

A forma com pesquisa de CASE fornece execução condicional baseada na verdade de expressões booleanas. A expressão_booleana de cada cláusula WHEN é avaliada sucessivamente, até que seja encontrada uma que produza true. Em seguida, as instruções correspondentes são executadas, e o controle passa para a próxima instrução após o END CASE. (As expressões WHEN subsequentes não são avaliadas.) Se não for encontrado nenhum resultado verdade, serão executadas as instruções do ELSE; mas se o ELSE não estiver presente, será lançada uma exceção CASE_NOT_FOUND.

A seguir está um exemplo:

DO $$
DECLARE
x int :=21;
msg text;
BEGIN
CASE
    WHEN x BETWEEN 0 AND 10 THEN
        msg := 'o valor está entre zero e dez';
    WHEN x BETWEEN 11 AND 20 THEN
        msg := 'o valor está entre onze e vinte';
END CASE;
END;
$$;
ERROR:  case não foi encontrado
HINT:  comando CASE está faltando a parte ELSE.
CONTEXT:  função PL/pgSQL inline_code_block linha 6 em CASE

Esta forma do CASE é totalmente equivalente a IF-THEN-ELSIF, exceto pela regra onde atingir uma cláusula ELSE omitida resulta em erro, em vez de não fazer nada.

41.6.5. Laços simples #

Com as instruções LOOP, EXIT, CONTINUE, WHILE, FOR e FOREACH, pode-se fazer com que a função escrita em PL/pgSQL repita uma série de comandos.

41.6.5.1. LOOP #

[ <<rótulo>> ]
LOOP
    instruções
END LOOP [ rótulo ];

LOOP define um laço incondicional, repetido indefinidamente até ser terminado por uma instrução EXIT ou RETURN. O rótulo opcional pode ser usado pelas instruções EXIT e CONTINUE dentro de laços aninhados, para especificar a qual laço estas instruções se referem.

41.6.5.2. EXIT #

EXIT [ rótulo ] [ WHEN expressão_booleana ];

Se nenhum rótulo for fornecido, o laço mais interno é terminado, e a instrução seguinte ao END LOOP é executada em seguida. Se rótulo for especificado, então deve ser o rótulo do laço corrente, ou de algum nível externo de laço ou bloco aninhado. Em seguida, o laço ou bloco especificado é terminado, e o controle continua com a instrução após o END correspondente do laço/bloco.

Se for especificado WHEN, a saída do laço ocorrerá somente se a expressão_booleana resultar em verdade. Caso contrário, o controle passará para a instrução após EXIT.

EXIT pode ser usado com todos os tipos de laço; não está limitado ao uso com laços incondicionais.

Quando usado em um bloco BEGIN, EXIT passa o controle para a próxima instrução após o final do bloco. Note que deve ser usado um rótulo para esta finalidade; um EXIT sem rótulo nunca é considerado correspondendo a um bloco BEGIN. (Esta é uma mudança com relação às versões do PostgreSQL pré-8.4, que permitiam que o EXIT sem rótulo correspondesse a um bloco BEGIN.)

Exemplos:

LOOP
    -- alguns cálculos
    IF count > 0 THEN
        EXIT;  -- sair do laço
    END IF;
END LOOP;

LOOP
    -- alguns cálculos
    EXIT WHEN count > 0;  -- mesmo resultado do exemplo anterior
END LOOP;

<<obloco>>
BEGIN
    -- alguns cálculos
    IF estoque > 100000 THEN
        EXIT obloco;  -- causa a saída do bloco BEGIN
    END IF;
    -- os cálculos aqui são ignorados quando estoque > 100000
END;

41.6.5.3. CONTINUE #

CONTINUE [ rótulo ] [ WHEN expressão_booleana ];

Se não for fornecido nenhum rótulo, a próxima iteração do laço mais interno será iniciada. Ou seja, todas as instruções restantes no corpo do laço são ignoradas, e o controle retorna à expressão de controle do laço (se houver) para determinar se é necessária outra iteração do laço. Se rótulo estiver presente, especifica o rótulo do laço cuja execução será continuada.

Se for especificado WHEN, a próxima iteração do laço será iniciada somente se a expressão_booleana resultar em verdade. Caso contrário, o controle passará para a instrução após o CONTINUE.

CONTINUE pode ser usado com todos os tipos de laços; não está limitado ao uso com laços incondicionais.

Exemplos:

LOOP
    -- alguns cálculos
    EXIT WHEN contador > 100;
    CONTINUE WHEN contador < 50;
    -- alguns cálculos para contador IN [50 .. 100]
END LOOP;

41.6.5.4. WHILE #

[ <<rótulo>> ]
WHILE expressão_booleana LOOP
    instruções
END LOOP [ rótulo ];

A instrução WHILE repete uma sequência de instruções enquanto a expressão_booleana for avaliada como verdade. A expressão é verificada antes de cada entrada no corpo do laço.

Por exemplo:

WHILE montante_devido > 0 AND saldo_vale_presente > 0 LOOP
    -- alguns cálculos
END LOOP;

WHILE NOT terminado LOOP
    -- alguns cálculos
END LOOP;

41.6.5.5. FOR (variante inteira) #

[ <<rótulo>> ]
FOR nome IN [ REVERSE ] expressão .. expressão [ BY expressão ] LOOP
    instruções
END LOOP [ rótulo ];

Esta forma de FOR cria um laço que itera sobre um intervalo de valores inteiros. A variável nome é definida automaticamente como sendo do tipo integer, existindo apenas dentro do laço (qualquer definição existente do nome da variável é ignorada dentro do laço). As duas expressões que fornecem o limite inferior e superior do intervalo são avaliadas uma vez ao entrar no laço. Se a cláusula BY não for especificada, o passo da iteração é 1, caso contrário é o valor especificado na cláusula BY, que novamente é avaliada uma vez na entrada do laço. Se for especificado REVERSE, o valor do passo será subtraído, em vez de adicionado, após cada iteração.

Alguns exemplos de laços FOR inteiros:

FOR i IN 1..10 LOOP
    -- i assume os valores 1,2,3,4,5,6,7,8,9,10 dentro do laço
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- i assume os valores 10,9,8,7,6,5,4,3,2,1 dentro do laço
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- i assume os valores 10,8,6,4,2 dentro do laço
END LOOP;

Se o limite inferior for maior que o limite superior (ou menor que, no caso REVERSE), o corpo do laço não será executado, e nenhum erro será lançado.

Se for anexado um rótulo ao laço FOR, então a variável inteira do laço poderá ser referenciada com um nome qualificado, usando este rótulo.

41.6.6. Percorrendo os resultados da consulta #

Usando um tipo diferente de laço FOR, é possível iterar pelos resultados de uma consulta e manipular os dados de acordo. A sintaxe é:

[ <<rótulo>> ]
FOR destino IN consulta LOOP
    instruções
END LOOP [ rótulo ];

O destino é uma variável tipo-registro, variável tipo-linha, ou uma lista separada por vírgulas de variáveis escalares. O destino é atribuído sucessivamente a cada linha resultante da consulta, e o corpo do laço é executado para cada linha de resultado. Aqui está um exemplo:

CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Atualização de todas as visões materializadas...';

    FOR mviews IN
       SELECT n.nspname AS mv_schema,
              c.relname AS mv_name,
              pg_catalog.pg_get_userbyid(c.relowner) AS owner
         FROM pg_catalog.pg_class c
    LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
        WHERE c.relkind = 'm'
     ORDER BY 1
    LOOP

        -- Agora "mviews" tem um registro com informações sobre a visão materializada

        RAISE NOTICE 'Atualizando a visão materializada %.% (dono: %)...',
                     quote_ident(mviews.mv_schema),
                     quote_ident(mviews.mv_name),
                     quote_ident(mviews.owner);
        EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
    END LOOP;

    RAISE NOTICE 'Concluída a atualização das visualizações materializadas.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Se o laço for terminado pela instrução EXIT, o último valor de linha atribuído ainda estará acessível após o laço.

A consulta usada neste tipo de instrução FOR pode ser qualquer comando SQL que retorne linhas para quem efetuou a chamada: SELECT é o caso mais comum, mas também podem ser usados INSERT, UPDATE, DELETE ou MERGE com a cláusula RETURNING. Alguns comandos utilitários, como o EXPLAIN, também funcionam.

As variáveis do PL/pgSQL são substituídas por parâmetros de consulta, e o plano de consulta é armazenado em cache para possível reutilização, conforme discutido em detalhes em Substituição de variável e Cache de plano.

A instrução FOR-IN-EXECUTE é outra maneira de iterar sobre linhas:

[ <<rótulo>> ]
FOR destino IN EXECUTE texto_da_expressão [ USING expressão [, ... ] ] LOOP
    instruções
END LOOP [ rótulo ];

É como a forma anterior, exceto pela consulta de origem ser especificada através de uma expressão de cadeia de caracteres avaliada e replanejada a cada entrada no laço FOR. Isto permite que o programador escolha entre a velocidade de uma consulta pré-planejada, ou a flexibilidade de uma consulta dinâmica, assim como com uma simples instrução EXECUTE. Como acontece com o EXECUTE, os valores dos parâmetros podem ser inseridos no comando dinâmico através do USING.

Outra maneira de especificar uma consulta cujos resultados devem ser iterados, é a declarar como um cursor. Isto é descrito em Laço através do resultado do cursor.

41.6.7. Percorrendo matrizes #

O laço FOREACH é muito parecido com o laço FOR, mas em vez de iterar pelas linhas retornadas por uma consulta SQL, itera pelos elementos de um valor matriz. (Em geral, FOREACH destina-se a percorrer os componentes de uma expressão de valor composto; variantes para percorrer tipos compostos, além de matrizes, poderão ser adicionadas no futuro.) A instrução FOREACH para percorrer uma matriz é:

[ <<rótulo>> ]
FOREACH destino [ SLICE número ] IN ARRAY expressão LOOP
    instruções
END LOOP [ rótulo ];

Sem SLICE, ou se for especificado SLICE 0, o laço itera pelos elementos individuais da matriz produzidos pela avaliação da expressão. O valor de cada elemento é atribuído à variável destino em sequência, e o corpo do laço é executado para cada elemento. Aqui está um exemplo de laço através dos elementos de uma matriz de inteiros:

CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

Os elementos são acessados na ordem do armazenamento, independentemente do número de dimensões da matriz. Embora o destino geralmente seja apenas uma única variável, pode ser uma lista de variáveis ao percorrer uma matriz de valores compostos (registros). Neste caso, para cada elemento da matriz as variáveis são atribuídas a partir de colunas sucessivas do valor composto.

Com um valor de SLICE positivo, FOREACH itera através de fatias da matriz em vez de elementos únicos. O valor de SLICE deve ser uma constante inteira não maior que o número de dimensões da matriz. A variável destino deve ser uma matriz, recebendo sucessivas fatias do valor da matriz, onde cada fatia tem o número de dimensões especificado por SLICE. Aqui está um exemplo de iteração através de fatias unidimensionais:

CREATE FUNCTION varre_linhas(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'row = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT varre_linhas(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTA:  row = {1,2,3}
NOTA:  row = {4,5,6}
NOTA:  row = {7,8,9}
NOTA:  row = {10,11,12}
 varre_linhas
--------------

(1 linha)

41.6.8. Captura de erros #

Por padrão, qualquer erro que ocorra em uma função PL/pgSQL interrompe a execução da função e da transação em volta dela. Os erros podem ser capturados e tratados usando um bloco BEGIN com a cláusula EXCEPTION. A sintaxe é uma extensão da sintaxe normal para um bloco BEGIN:

[ <<rótulo>> ]
[ DECLARE
    declarações ]
BEGIN
    instruções
EXCEPTION
    WHEN condição [ OR condição ... ] THEN
        instruções_do_tratador
    [ WHEN condição [ OR condição ... ] THEN
          instruções_do_tratador
      ... ]
END;

Se não ocorrer nenhum erro, esta forma de bloco simplesmente executa todas as instruções, e então o controle passa para a próxima instrução após o END. Mas se ocorrer um erro nas instruções, o processamento das instruções posteriores é abandonado, e o controle passa para a lista EXCEPTION. A lista é procurada pela primeira condição que corresponder ao erro ocorrido. Se for encontrada uma correspondência, as instruções_do_tratador correspondentes serão executadas, e o controle passará para a próxima instrução após o END. Se não for encontrada nenhuma correspondência, o erro se propagará como se a cláusula EXCEPTION não existisse: o erro pode ser capturado por um bloco envoltório contendo EXCEPTION, mas caso não haja nenhum, o erro interromperá o processamento da função.

Os nomes das condições podem ser qualquer um dos mostrados em Códigos de erro do PostgreSQL. Um nome de categoria corresponde a qualquer erro dentro de sua categoria. O nome de condição especial OTHERS corresponde a todos os tipos de erro, exceto QUERY_CANCELED e ASSERT_FAILURE. (É possível, mas muitas vezes imprudente, interceptar estes dois tipos de erro pelo nome.) Os nomes das condições não diferenciam letras maiúsculas de minúsculas. Além disso, uma condição de erro pode ser especificada pelo código SQLSTATE; por exemplo, estes dois fragmentos são equivalentes:

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

Se ocorrer um novo erro dentro das instruções_do_tratador selecionadas, este erro não poderá ser capturado por esta cláusula EXCEPTION, mas será propagado. Uma cláusula EXCEPTION envoltória poderá capturá-lo.

Quando um erro é capturado pela cláusula EXCEPTION, as variáveis locais da função PL/pgSQL permanecem como estavam quando o erro ocorreu, mas todas as alterações no estado persistente do banco de dados dentro o bloco é desfeito. Como exemplo, considere este fragmento:

INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
    UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
    x := x + 1;
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'capturado division_by_zero';
        RETURN x;
END;

Quando o controle atingir a atribuição para y, falhará com um erro de division_by_zero. Isto será capturado pela cláusula EXCEPTION. O valor retornado na instrução RETURN será o valor incrementado de x, mas os efeitos do comando UPDATE serão desfeitos. O comando INSERT que precede o bloco não é desfeito, portanto, o resultado final é que o banco de dados contém Tom Jones, e não Joe Jones.

Dica

Um bloco contendo uma cláusula EXCEPTION é significativamente mais caro para entrar e sair do que um bloco sem ela. Portanto, não deve ser usado EXCEPTION sem necessidade.

Exemplo 41.4. Exceções com UPDATE/INSERT

Este exemplo usa o tratamento de exceção para executar UPDATE ou INSERT, conforme apropriado. Recomenda-se que as aplicações usem INSERT com ON CONFLICT DO UPDATE, em vez de usar este método. Este exemplo serve, principalmente, para ilustrar o uso de estruturas de fluxo de controle do PL/pgSQL:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- primeiro vamos tentar atualizar a chave
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- se não existir, então vamos tentar inserir a chave
        -- se outra pessoa inserir a mesma chave ao mesmo tempo,
        -- então pode acontecer uma falha de chave única
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Não faça nada, e continue o laço
            -- para tentar o UPDATE novamente.
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

Este código assume que o erro unique_violation é causado pelo INSERT, e não por, digamos, um INSERT em uma função de gatilho na tabela. O código também pode se comportar mal quando há mais de um índice único na tabela, porque repetirá a operação independentemente de qual índice causou o erro. Pode ser obtida uma segurança maior usando os recursos discutidos a seguir para verificar se o erro capturado era o esperado.


41.6.8.1. Obtenção de informações sobre o erro #

Os tratadores de exceção frequentemente precisam identificar o erro específico ocorrido. Existem duas maneiras de obter informações sobre a exceção corrente no PL/pgSQL: variáveis especiais, e o comando GET STACKED DIAGNOSTICS.

Dentro de um tratador de exceção, a variável especial SQLSTATE contém o código de erro que corresponde à exceção que foi levantada (veja a Tabela de códigos de erro do PostgreSQL para obter a lista de possíveis códigos de erro). A variável especial SQLERRM contém a mensagem de erro associada à exceção. Estas variáveis não estão definidas fora dos tratadores de exceção.

Dentro de um tratador de exceção, também é possível obter informações sobre a exceção corrente usando o comando GET STACKED DIAGNOSTICS, que tem a forma:

GET STACKED DIAGNOSTICS variável { = | := } item [ , ... ];

Cada item é uma palavra-chave que identifica um valor de status a ser atribuído à variável especificada (que deve ser do tipo de dados correto para recebê-lo). Os itens de status atualmente disponíveis estão mostrados na Tabela 41.2.

Tabela 41.2. Itens de diagnóstico de erro

NomeTipoDescrição
RETURNED_SQLSTATEtexto código de erro SQLSTATE da exceção
COLUMN_NAMEtexto nome da coluna relacionada à exceção
CONSTRAINT_NAMEtexto nome da restrição relacionada à exceção
PG_DATATYPE_NAMEtexto nome do tipo de dados relacionado à exceção
MESSAGE_TEXTtexto texto da mensagem principal da exceção
TABLE_NAMEtexto nome da tabela relacionada à exceção
SCHEMA_NAMEtexto nome do esquema relacionado à exceção
PG_EXCEPTION_DETAILtexto texto da mensagem de detalhes da exceção, se houver
PG_EXCEPTION_HINTtexto texto da mensagem de dica da exceção, se houver
PG_EXCEPTION_CONTEXTtextlinha(s) de texto descrevendo a pilha de chamadas no momento da exceção (veja Obtenção de informações sobre o local da execução)

Se a exceção não definir um valor para um item, será retornada uma cadeia de caracteres vazia.

Aqui está um exemplo:

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- algum processamento que pode causar uma exceção
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

41.6.9. Obtenção de informações sobre o local da execução #

O comando GET DIAGNOSTICS, descrito anteriormente em Obtenção do status do resultado, recupera informações sobre o estado da execução corrente (enquanto o comando GET STACKED DIAGNOSTICS, discutido acima, mostra informações sobre o estado da execução de um erro anterior). Seu item de status PG_CONTEXT é útil para identificar o local corrente da execução. PG_CONTEXT retorna uma cadeia de caracteres com linha(s) de texto descrevendo a pilha de chamadas. A primeira linha refere-se à função corrente e ao comando GET DIAGNOSTICS atualmente em execução. A segunda, e quaisquer linhas subsequentes, referem-se a funções de chamada mais acima na pilha de chamadas. Por exemplo:

CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
  RETURN inner_func();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
  stack text;
BEGIN
  GET DIAGNOSTICS stack = PG_CONTEXT;
  RAISE NOTICE E'--- Call Stack ---\n%', stack;
  RETURN 1;
END;
$$ LANGUAGE plpgsql;

SELECT outer_func();

NOTA:  --- Call Stack ---
função PL/pgSQL inner_func() linha 5 em GET DIAGNOSTICS
função PL/pgSQL outer_func() linha 3 em RETURN
 outer_func
------------
          1
(1 linha)

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT retorna o mesmo tipo de rastreamento de pilha, mas descrevendo o local em que o erro foi detectado, em vez do local corrente.