Procedury składowane w PostgreSQL/Język PLpgSQL/Wyjątki

Łapanie wyjątków

edytuj

Domyślnie, gdy pojawi się wyjątek funkcja jest przerywana, a błąd zgłaszany aplikacji.

W procedurze można przechwycić wyjątek, w sposób analogiczny do znanego z C++ czy Javy bloku try { } catch {}. Odpowiada za to rozszerzony blok kodu o następującej składni:

[ <<etykieta>> ]
[ DECLARE
    deklaracje zmiennych ]
BEGIN
    instrukcje
EXCEPTION
    WHEN lista wyjątków1 THEN
        instrukcje
    [ WHEN lista wyjątków2 THEN
        instrukcje ]
END;

W bloku obsługiwane są wyłącznie błędy zgłoszone przez instrukcje umieszczone po słowie BEGIN, błędy zgłoszone w części deklaracyjnej są przekazywane do bloku nadrzędnego.

Lista wyjątków zawiera nazwy symboliczne lub kody błędów, rozdzielone słowem kluczowym OR. Instrukcje podane po THEN są wykonywane, gdy zgłoszony wyjątek znajduje się na liście. Zamiast listy wyjątków można podać słowo kluczowe OTHERS, które łapie wszystkie niewychwycone wcześniej wyjątki.

Jeśli żaden blok WHEN nie obsłuży wyjątku, jest on przekazywany do bloku nadrzędnego.

Należy pamiętać, że bloki z obsługą wyjątków są wolniejsze, niż tradycyjne - patrz Transakcje.

Przykład 1

edytuj
CREATE TABLE osoby (
    id          integer PRIMARY KEY,
    imie        text NOT NULL,
    nazwisko    text NOT NULL,
    wiek        integer NOT NULL CHECK (wiek > 0)
);

CREATE OR REPLACE FUNCTION dodaj_osobe(integer, text, text, integer) RETURNS text AS $$
    BEGIN
        INSERT INTO osoby VALUES ($1, $2, $3, $4);
        RETURN 'OK!';
    EXCEPTION
        WHEN UNIQUE_VIOLATION THEN
            RETURN 'powtórzony identyfikator osoby';

        WHEN CHECK_VIOLATION THEN
            RETURN 'wiek nie może być mniejszy ani równy zero';

        WHEN OTHERS THEN
            -- UWAGA: bezsensowny komunikat! (ale to tylko przykład)
            RETURN 'inny błąd';
    END;
$$ LANGUAGE 'plpgsql';

TRUNCATE osoby;
SELECT dodaj_osobe(1, 'Jan', 'Kowalski', 33);
SELECT dodaj_osobe(1, 'Jan', 'Kowalski', 33);
SELECT dodaj_osobe(2, 'Kazimierz', 'Nowak', 0);
SELECT dodaj_osobe(3, NULL, 'Nowak', 0);

Poniżej wynik. Komunikat "inny błąd" jest wynikiem naruszenie ograniczenia NOT NULL na kolumnie imie.

 dodaj_osobe 
-------------
 OK!
(1 row)

          dodaj_osobe           
--------------------------------
 powtórzony identyfikator osoby
(1 row)

                dodaj_osobe                
-------------------------------------------
 wiek nie może być mniejszy ani równy zero
(1 row)

 dodaj_osobe 
-------------
 inny błąd
(1 row)

Przykład 2

edytuj

Błąd z części deklaracyjnej nie zostanie złapany.

CREATE OR REPLACE FUNCTION test() RETURNS text AS $$
    DECLARE
        k integer := 1/0;
    BEGIN
        RETURN 'ok';
    EXCEPTION
        WHEN division_by_zero THEN
            RETURN 'Dzielenie przez zero!';
    END;
$$ LANGUAGE 'plpgsql';

Informacje o wyjątku

edytuj

W bloku po WHEN dostępne są dwie predefiniowane zmienne:

  • SQLSTATE - kod błędu,
  • SQLERRM - komunikat błędu.

Poprawiony przykład

	BEGIN
		...
	EXCEPION
		...

        WHEN OTHERS THEN
            -- teraz lepiej
            RETURN 'błąd ' || SQLSTATE || ': ' || SQLERRM;

i efekt:

                             dodaj_osobe                              
----------------------------------------------------------------------
 błąd 23502: null value in column "imie" violates not-null constraint

Rozszerzone informacje o wyjątku

edytuj

Od wersji 9.2 dostępna będzie instrukcja GET STACKED DIAGNOSTICS, która pozwoli odczytać szczegółowe informacje o złapanym wyjątku. Składnia:

GET STACKED DIAGNOSTICS zmienna = nazwa_parametru [, zmienna = nazwa_parametru  [, ... ] ];

Gdzie zmienna jest typu napisowego (text), natomiast nazwą parametru może być:

  • RETURNED_SQLSTATE - kod błędu; odpowiada zmiennej SQLSTATE;
  • MESSAGE_TEXT - komunikat błędu; odpowiada zmiennej SQLERRM;
  • PG_EXCEPTION_DETAIL - szczegóły błędu;
  • PG_EXCEPTION_HINT - podpowiedź;
  • PG_EXCEPTION_CONTEXT - ślad stosu: w kolejnych wierszach zawiera instrukcję, która spowodowała błąd, oraz nazwę funkcji i numer wiersza, w którym ta instrukcja się znajduje.

Przykład

edytuj
CREATE OR REPLACE FUNCTION funkcja_a() RETURNS void AS $$
    BEGIN
        RAISE 'Błąd' USING DETAIL = 'Szczegóły', HINT = 'Podpowiedź';
    END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION funkcja_b() RETURNS void AS $$
    BEGIN
        PERFORM funkcja_a();
    END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION test() RETURNS text AS $$
    BEGIN
        PERFORM funkcja_b();
        RETURN 'ok';
    EXCEPTION
        WHEN OTHERS THEN
            DECLARE
                state   text;
                msg     text;
                detail  text;
                hint    text;
                context text;
            BEGIN
                GET STACKED DIAGNOSTICS
                    state   = RETURNED_SQLSTATE,
                    msg     = MESSAGE_TEXT,
                    detail  = PG_EXCEPTION_DETAIL,
                    hint    = PG_EXCEPTION_HINT,
                    context = PG_EXCEPTION_CONTEXT;

                RETURN 'RETURNED_SQLSTATE = '    || state  || E'\n' ||
                       'MESSAGE_TEXT = '         || msg    || E'\n' ||
                       'PG_EXCEPTION_DETAIL = '  || detail || E'\n' ||
                       'PG_EXCEPTION_HINT = '    || hint   || E'\n' ||
                      E'PG_EXCEPTION_CONTEXT = \n' || context;
            END;
    END;
$$ LANGUAGE 'plpgsql';

Wynik:

$ SELECT test();
                      test                       
-------------------------------------------------
 RETURNED_SQLSTATE = P0001                      +
 MESSAGE_TEXT = Błąd                            +
 PG_EXCEPTION_DETAIL = Szczegóły                +
 PG_EXCEPTION_HINT = Podpowiedź                 +
 PG_EXCEPTION_CONTEXT =                         +
 SQL statement "SELECT funkcja_a()"             +
 PL/pgSQL function "funkcja_b" line 3 at PERFORM+
 SQL statement "SELECT funkcja_b()"             +
 PL/pgSQL function "test" line 3 at PERFORM

Zgłaszanie wyjątków

edytuj

Instrukcja RAISE pozwala zgłosić dowolne wyjątki. Przeczytaj jej dokładny opis.

Ponowne podnoszenie wyjątków (rethrow)

edytuj

Bezparametrowa instrukcja RAISE użyta w bloku WHEN powoduje podniesienie wyłapanego wyjątku. Np.

WHEN unique_violation OR not_null_violation THEN
	-- komunikat
	RAISE NOTICE 'powtórzony identyfikator osoby lub pusta wartość';

	-- zgłoszenie unique_violation albo not_null_violation
	RAISE;
...