7. blok - část A Jazyk PL/SQL - zpracování chyb, řízení transakcí Studijní cíl Tento blok je věnován ošetření chyb a řízení transakcí v kódu PL/SQL. Doba nutná k nastudování 2-3 hodiny Průvodce studiem Při studiu tohoto bloku se předpokládá, že čtenář je obeznámen s jazykem SQL, je schopen napsat jednoduchý program v libovolném programovacím jazyce a zná základy jazyka PL/SQL. 1. Zpracovávání chyb Dobře napsané programy musí být schopny správně zpracovávat chyby a umět se z nich vzpamatovat. V PL/SQL se s chybami pracuje pomocí výjimek a zachytávání výjimek. Výjimky je možné navázat na chyby Oracle nebo je možné si definovat vlastní uživatelsky definované chyby. V následujícím textu se seznámíme se syntaxí pro práci s výjimkami a s pravidly pro šíření výjimek. Výjimky v PL/SQL se podobají výjimkám v jazyce Java. Ale na rozdíl od výjimek v jazyce Java nejsou výjimky v PL/SQL objekty a nemají definovány žádné metody. V PL/SQL se mohou objevit 2 typy chyb: chyby při překladu, které hlásí překladač a které je nezbytné opravit, aby mohl být předkompilován a chyby za běhu programu, které zpracovávají zachytávače výjimek. Jestliže doje k chybě za běhu programu, je vyvolána výjimka. Provádění kódu (řízení běhu programu) poté přejde do části zachytávače výjimek, která je oddělena od zbytku programu. Toto oddělení má kromě vlastního zpřehlednění kódu také tu výhodu, že zde budou zachycenyy všechny chyby. Program tedy nebude pokračovat dál od příkazu, který způsobil chybu, ale vždy přejde do zachytávače výjimek a poté do libovolného vnějšího bloku. Existují 2 typy výjimek: předdefinované a uživatelsky definované. 1
Předdefinované výjimky Oracle má mnoho předdefinovaných výjimek, které odpovídají běžným chybám, viz následující tabulka: Výjimka Oracle chyba Hodnota SQLCODE Vyvolána je: ACCESS_INTO_NULL 06530 COLLECTION_IS_NULL 06531 CURSOR_ALREADY _OPEN DUP_VAL_ON_INDEX INVALID_CURSOR INVALID_NUMBER LOGIN_DENIED NO_DATA_FOUND NOT_LOGGED_ON 06511 00001 01001 01722 01017 01403 01012-6530 Přiřazení hodnoty k atributu neinicializovaného (null) objektu. -6531 Použití jiné sběrné metody (collection method) než EXISTS na neinicializovanou (atomicky null) vnořenou tabulku nebo pole (varray) nebo přiřazení hodnoty do položek neinicializované vnořené tabulky nebo pole -6511 Snaha o otevření již jednou otevřeného kurzoru (cursor), před opětovným otevřením musíte kurzor nejdříve zavřít, kurzorový FOR cyklus otevírá kurzor automaticky, takže jej nelze uvnitř cyklu opět otevřít. -1 Snaha o uložení totožné (již existující) hodnoty do sloupce, který je označen jako unique index - index specifických hodnot. -1001 Nepovolená operace s kurzorem (cursor), například zavření neotevřeného kurzoru. -1722 Selhání převodu mezi řetězcem znaků a číslem v příkazu SQL z toho důvodu, že řetězec neobsahuje platné číslo. V procedurálním příkazu je vyvolána výjimka VALUE_ERROR. -1017 Snaha nalogovat se na Oracle server s neplatným jménem (username) a/nebo heslem (password). +100 Pokud příkaz SELECT INTO nevrátí žádné řádky nebo se odkazujete na smazaný prvek vnořené tabulky nebo neinicializovaný prvek index-by tabulky. Od příkazu FETCH se případně dá očekávat navrácení prázdného řádku (no rows) a v tomto případě výjimka není vyvolána. SQL kolektivní (group) funkce jako AVG a SUM vracejí vždy hodnotu nebo null. Proto také příkaz SELECT INTO volající group funkci nikdy nevyvolá výjimku NO_DATA_FOUND. -1012 Pokud se PL/SQL program pokouší provést databázovou operaci bez předchozího připojení k serveru Oracle. 2
PROGRAM_ERROR 06501 ROWTYPE_MISMATCH 06504 STORAGE_ERROR SUBSCRIPT_BEYOND _COUNT SUBSCRIPT_OUTSIDE _LIMIT TIMEOUT_ON _RESOURCE TOO_MANY_ROWS VALUE_ERROR ZERO_DIVIDE 06500 06533 06532 00051 01422 06502 01476-6501 Vnitřní problém (internal problem) při běhu programu. -6504 Hlavní (host) a PL/SQL kurzorová proměnná vyžádaná dosazením mají nekompatibilní návratové typy. Například, když přenecháte otevřený host kurzor uloženému podprogramu, pak návratové typy aktuálních a formálních parametrů musí být kompatibilní. -6500 PL/SQL vyčerpalo paměť, nebo je paměť poškozená. -6533 Odkaz na prvek vnořené tabulky (nested table) či pole (varray) s číslem indexu větším než je počet dostupných prvků. -6532 Odkaz na prvek vnořené tabulky (nested table) či pole (varray) s číslem indexu, který je mimo povolený rozsah (např. -1). -51 Vypršel čas (time-out), během kterého Oracle čeká na prostředky (resource). -1422 Příkaz SELECT INTO vrací více než jeden řádek. -6502 Chyba aritmetická, převodní, zkrácení nebo omezení velikosti. Například, když dosadíte hodnotu ze sloupce do znakové proměnné a tato hodnota je delší než nedeklarovaná délka proměnné. V procedurálních příkazech je výjimka vyvolána, pokud selže převod mezi řetězcem a číslem (v SQL je to výjimka INVALID_NUMBER). -1476 Dělení nulou. Příklad: DECLARE v_jmeno ucitel..jmeno%type; v_id ucitel..id%type; SELECT jmeno, Id INTO v_jmeno, v_id FROM ucitel WHERE Id=2; DBMS_OUTPUT.PUT_LINE('Jméno: ' v_jmeno); DBMS_OUTPUT.PUT_LINE('Id: ' v_id); EXCEPTION -- ošetření výjimky při nenalezení dat WHEN NO_DATA_FOUND THEN 3
DBMS_OUTPUT.PUT_LINE('Data nenalezena'); -- ošetření výjimky při nalezení více řádků WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE('Mnoho řádků'); Uživatelsky definované výjimky V PL/SQL má uživatel možnost nadefinovat si vlastní výjimky. Tyto výjimky se deklarují v deklarační části, vyvolávají se v exekuční části příkazem RAISE a zpracovávají se v oblasti výjimek. Deklarace výjimky začíná jejím jménem následovaným klíčovým slovem EXCEPTION. Pro vlastní výjimky je SQLCODE rovno 1 a SQLERRM vrací Text User-Defined Exception. Syntaxe DECLARE <název výjimky> EXCEPTION; <příkazy>; RAISE <název výjimky>; EXCEPTION WHEN <název výjimky> THEN <příkazy>; Příklad deklarace a vyvolání výjimky pojmenované PRILIS_MNOHO_TRPASLIKU: : DECLARE PRILIS_MNOHO_TRPASLIKU EXCEPTION; v_pocet_trpasliku NUMBER; select count(*) INTO v_pocet_trpasliku FROM trpaslici; IF v_pocet_trpasliku > 7 THEN RAISE PRILIS_MNOHO_TRPASLIKU; END IF; EXCEPTION WHEN PRILIS_MNOHO_TRPASLIKU THEN DBMS_OUTPUT.PUT_LINE('Trpaslíků může být max. 7!' '); 4
Použití raise_application_error Balíček DBMS_STANDARD dodávaný spolu s Oracle poskytuje jazykové prostředky, které mohou vašim aplikacím napomoci při spolupráci s Oracle. Například procedura raise_application_error umožňuje zveřejnit uživatelsky definované chybové hlášky z uložených podprogramů (stored subprograms). Touto cestou můžete své aplikaci oznamovat chyby a vyhnout se vracení neošetřených chyb. Příkaz raise_application_error má následující syntax: RAISE_APPLICATION_ERROR(error_number, message[, {TRUE FALSE}]); kde error_number je záporné celé číslo (integer) v rozsahu -20000.. -20999 a message je řetězec maximální délky 2048 bytů. Jestliže je třetí nepovinný parameter TRUE, chyba je uložena do zásobníku, pokud je FALSE (default), chyba nahradí všechny dosud uložené chyby. Aplikace může volat raise_application_error pouze ze spustitelného uloženého podprogramu. Je-li zavolána, raise_application_error ukončí podprogram a vrátí uživatelsky definovanou chybu (číslo chyby) a zprávu aplikaci. Číslo chyby a zpráva pak může být odchytnuta stejně jako každá jiná Oracle chyba. V následujícím příkladu je zavolána procedura raise_application_error, pokud není uvedena mzda u daného zaměstnance, v opačném případě provede navýšení mzdy: DECLARE v_mzda NUMBER; v_id NUMBER := 1; SELECT mzda INTO v_mzda FROM zamestnanci WHERE zam_id = v_id; IF v_mzda IS NULL THEN raise_application_error(-20101, 'Mzda neuvedena'); ELSE UPDATE zamestnanci SET mzda = p_mzda * 1,05 WHERE zam_id = v_id; END IF; Volající modul obdrží výjimku, kterou může zpracovat pomocí funkcí na zpracování chyb (error-reporting functions) SQLCODE a SQLERRM v handleru OTHERS. 5
Zachytávání výjimek Jakmile dojde k výjimce, přechází tok programu v daném bloku do oblasti výjimek. Tato oblast se skládá ze zachytávačů pro některé (WHEN <název výjimky>) nebo všechny ostatní (OTHERS). Za klauzulí THEN je uveden kód, který se v daném případě provede. Všeobecná syntaxe pro zpracování výjimek: EXCEPTION [WHEN <název výjimky1> THEN <příkazy>; [WHEN <název výjimky2> OR <název výjimky3> THEN <příkazy>; [OTHERS THEN <příkazy> >;] Zachytávač ostatních výjimek (OTHERS) zachytí všechny výjimky neošetřené v klauzuli WHEN. Klauzule WHEN OTHERS zachytí všechny výjimky. Je vhodné mít tento univerzální zachytávač na nejvyšší úrovni programu (nejvyšším bloku), protože poté z programu neunikne řádná výjimka. Jinak hrozí, že se chyba bude šířit do vnějšího prostředí. Když je vyvolána výjimka v exekuční oblasti bloku, postupuje řízení běhu programu podle následujících pravidel: 1. Jestliže má aktuální blok pro danou výjimku zachytávač, spustí jej a blok dokončí jako úspěšný. Řízení pak přechází do vnějšího bloku. 2. Jestliže neexistuje pro danou výjimku v daném bloku zachytávač, dojde k přenosu chyby do vnějšího (bloku). Pro tento blok se provede krok 1 - pokud existuje zachytávač pro danou výjimku, jinak se opakuje přenos do dalšího vnějšího bloku. 3. Jestliže vnější blok neexistuje, výjimka se dostane do volajícího prostředí. Pokud je vyvolána výjimka v deklarační oblasti při přiřazování, přechází výjimka ihned do vnějšího bloku. Pokud je vyvolána výjimka v zachytávači výjimek, přechází opět řízení ihned do vnějšího bloku. Neboli platí, že v danou chvíli může být aktivní pouze jediná výjimka. 6
Příklad 1: Po vyvolání výjimky A je tato zachycena ve vnitřním bloku a program pokračuje dalším příkazem ve vnějším bloku. Příklad 2: Po vyvolání výjimky A není tato zachycena vnitřním bloku a výjimka je propagována vnějšího bloku, kde je v sekci výjimek zachycena. ve do Příklad 3: Po vyvolání výjimky A není tato zachycena ve vnitřním bloku a výjimka je propagována do vnějšího bloku, bohužel ani zde není tato výjimka zachycena. Další vnější programový blok již neexistuje a PL/SQL proto propaguje výjimku do volajícího prostředí. 7
V zachytávači výjimek lze ke zjištění informací o chybě použít funkcí SQLCODE a SQLERRM (číslo chyby a chybová zpráva). Pro vnitřní výjimky SQLCODE vrací číslo chyby (Oracle error number), které je záporné (tedy kromě výjimky no data found, kdy SQLCODE vrací +100). Funkce SQLERRM vrací příslušnou chybovou zprávu (začínající kódem dané chyby). Pro uživatelské výjimky (user-defined exceptions) vrací fce SQLCODE +1 a SQLERRM vrací 'User-Defined Exception' Příklad: DECLARE v_vysledek NUMBER(9,2); v_vysledek := 5/0; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(' Chyba '); DBMS_OUTPUT.PUT_LINE('Kód chyby:' SQLCODE); DBMS_OUTPUT.PUT_LINE('Popis chyby:' SQLERRM); Při práci s výjimkami mohou vzniknout následující potřeby: 1) pokračování po vyvolání výjimky v provádění příkazů v rámci daného bloku - tuto potřebu ošetříme vložením dalšího (vnitřního) bloku do daného bloku a výjimku zpracujeme v rámci tohoto nově vloženého bloku. 2) použití vyhledávací proměnné - pokud v rámci bloku existuje více příkazů, které mohou vyvolat stejnou výjimku, je vhodné použít pomocnou proměnnou pro uložení označení příkazu, který se bude následně provádět. V zachytávači pak můžeme s hodnotou této proměnné pracovat, například ji logovat spolu s informací o vyskytnuvší se chybě. 3) opakování transakce - pokud chcete po vyvolání výjimky namísto přerušení transakce tuto transakci zopakovat, je potřeba transakci uzavřít do jednoho vnitřního bloku a poté umístit tento vnitřní blok do cyklu. Před započetím transakce je potřeba označit záchranný bod (savepoint). Pokud je transakce úspěšná, přijmete ji a opustíte cyklus. Pokud však selže, kontrola je předána zachytávači výjimek tohoto vnitřního bloku, kde odrolujete k savepointu a pokusíte se napravit problém. Program poté bude pokračovat dalším cyklem. Je však vhodné, aby cyklus měl jen konečný počet pokusů a bylo možné program ukončit. 8
2. Řízení transakcí v PL/SQL Z jedné z předchozích lekcí již znáte, že transakce zajišťují konzistentnost databáze a jejich vlastnosti označované zkratkou ACIT chrání transakční data před narušením. Víme, jak se chovají transakce v jazyce SQL, a zajímá nás, jak to bude v případě, kdy operace DML budou s daty pracovat uprostřed bloků PL/SQL. Pokud váš program selže uprostřed transakce, Oracle detekuje chybu a vrátí transakci - odroluje na začátek. Proto je databáze obnovena do původního stavu automaticky. Řízení transakcí se zajišťuje pomocí příkazů COMMIT, ROLLBACK, SAVEPOINT a SET TRANSACTION. COMMIT je trvalé potvrzení změn databáze provedených během aktuální transakce. ROLLBACK ukončí aktuální transakci a zruší všechny změny provedené od začátku transakce. SAVEPOINT označí aktuální bod zpracování transakce. Používá se pro odrolování transakce k bodu návratu příkazem ROLLBACK TO <bod návratu>. SET TRANSAKCE nastaví transakční vlastnosti - například úroveň izolace. Autonomní transakce Autonomní transakce probíhají samostatně - tj. bez transakčního dohledu z nadřízené transakce. To znamená, že jestliže v rámci autonomní nebo hlavní transakce použijeme potvrzení nebo odrolování, druhou transakci to neovlivní. Autonomní transakce se používají například pro zápis do protokolu událostí, který si sami chceme vytvářet. To nám umožní monitorovat aktivitu bez ohledu na její výsledek (tím mějme na mysli hlavní transakci), a opačně, úspěch či selhání zápisu do protokolu (pracuje v autonomní transakci) nemá vliv na hlavní transakci. Pro vytvoření autonomní transakce se použije direktiva (pragma) AUTONOMOUS_TRANSACTION, která se umístí do deklarační oblasti bloku. Autonomní kód můžeme použít ve funkcích, procedurách, spouštích, balíčcích a objektových typech. Chování si můžeme vysvětlit na následujícím příkladu. CREATE TABLE at_test ( id NUMBER NOT NULL, popis VARCHAR2( (50) NOT NULL ); INSERT INTO at_test (id, INSERT INTO at_test (id, popis) VALUES (1, 'Popis pro 1'); popis) VALUES (2, 'Popis pro 1'); Příkaz SELECT * FROM at_test; vrátí 2 řádky. 9
Nyní vložíme 8 řádků s použitím kódu s autonomní transakcí: DECLARE PRAGMA AUTONOMOUS_TRANSACTION; FOR i IN 3.. 10 LOOP INSERT INTO at_test (id, popis) VALUES (i, 'Popis pro ' i); END LOOP; COMMIT; Jak vidíme, celá autonomní transakce byla potvrzena příkazem COMMIT. Pokud nyní provedeme příkaz ROLLBACK; Dotaz SELECT * FROM at_test; vrátí 8 řádků. Příkaz ROLLBACK je tedy aplikován pouze na řádky vložené z hlavní transakce, ale na řádky vložené autonomní transakcí nemá vliv. Druhý příklad ukazuje způsob zalogování chyby do tabulky error_logs, která bude použita jako protokol událostí. Nejdříve vytvoříme tuto tabulkuu a sekvenci pro generování m hodnot primárního klíče: CREATE TABLE error_logs ( id NUMBER(10) NOT NULL, log_timestamp TIMESTAMP NOT NULL, error_message VARCHAR2(4000), CONSTRAINT error_logs_pk PRIMARY KEY (id) ); CREATE SEQUENCE error_logs_seq; Dále vytvoříme proceduru pro zalogování chyby jako autonomní transakci: CREATE OR REPLACE PROCEDURE log_errors (p_error_message IN VARCHAR2) AS PRAGMA AUTONOMOUS_TRANSACTION; INSERT INTO error_logs (id, log_timestamp, error_message) VALUES (error_logs_seq.nextval, SYSTIMESTAMP, p_error_message); COMMIT; 10
Nyní provedeme kód PL/SQL, který vyvolá chybu, která je zachycena a uložena. -- Platný příkaz INSERT INTO at_test (id, description) VALUES (998, 'Description for 998'); -- Vynutit neplatný INSERT INSERT INTO at_test (id, description) VALUES (999, NULL); EXCEPTION WHEN OTHERS THEN log_errors (p_error_message => SQLERRM); ROLLBACK; Po vykonání kódu dostaneme hlášku: completed. PL/SQL procedure successfully Existenci dat v tabulce at_test zjistíme příkazem: SELECT * FROM at_test WHERE id >= 998; Odpovědí bude: no rows selected Podíváme se na obsah tabulky error_logs příkazem SELECT * FROM error_logs; Získáme výsledek: ID LOG_TIMESTAMP ERROR_MESSAGE ---- -------------------------------- ---------------------------------- 1 28-FEB-2012 11:10:10.107625 01400: cannot insert NULL into ("SCHEMANAME"."AT_TEST"."DESCRIPTION") Opět vidíme, že ROLLBACK hlavní transakce nezpůsobil změny v potvrzené autonomní transakci. Výjimky a transakce Vyvolání výjimky ani konec bloku neukončují transakci. Jedině v případě, kdy neošetřená výjimka je v bloku nejvyšší úrovně, která se rozšíří do volajícího prostředí, server automaticky transakci odvolá. 11
Pojmy k zapamatování Příkazy a funkce: PL/SQL, výjimky, zachytávač, řízení transakcí, EXCEPTION, RAISE, COMMIT, SAVEPOINT, ROLLBACK Problém: řízení toku programu při vyvolání chyby, zachycení výjimky Shrnutí V této lekci jste se seznámili s detekcí a zpracováním chyb v kódu PL/SQL. Velké množství chyb je předdefinováno systémem, ale můžeme vytvářet i uživatelsky definované chyby a tyto programově vyvolat. Důležitou částí je pak pochopení pravidel pro šíření výjimek (není-li výjimka zachycena v lokálním bloku, šíří se do bloku vnějšího, a pokud není zachycena ani v bloku nejvyšší úrovně, je ošetřena ve volajícím prostředí). Další část dokumentu se věnovala transakčnímu zpracování. Z hlediska transakčního zpracování je důležité zejména neopomenout příkazy pro řízení transakcí u těch programových kódů, které budouu realizovat DML příkazy. Otázky na procvičení 1. Jak se pracuje s uživatelsky definovanými výjimkami? 2. Popište zachytávání a šíření chyb mezi bloky PL/SQL. 3. K čemu slouží funkce SQLCODE a SQLERRM? 4. Jakým způsobem zajistit, aby po výskytu chyby zůstal běh programu v daném modulu? 5. Jak probíhá zpracování transakcí v modulech PL/SQL? Odkazy a další studijní prameny http://www.oracle-base.com/articles/misc/autonomoustransactions.php http://www.techonthenet.com/oracle (syntaxe příkazů SQL jazyka a funkcí) http://www.oracle.com/ /technetwork/database/enterpriseedition/documentation (dokumentace k databázové platformě Oracle) http://www.penguin.cz/ /noviny/?id=chip/index (seriál Databáze standardu SQL z časopisu CHIP) Odkazy a další studijní prameny LACKO, L. Oracle, správa, programování a použití databázového systému. Praha: Computer Press, 2007. ISBN 80-251-1490-2. URMAN, S., HARDMAN, R., MCLAUGHLIN, M. Oracle - programování v PL/SQL. Computer Press, 2008. ISBN 978-80-251-1870-2 12