Embedded C – výjimky, kurzory Šárka Hlušičková
Obsah ●
Indikátorové proměnné – informace o obsahu hostitelských proměnných
●
SQLCA, whenever klauzule – ošetření běhových chyb
●
Kurzory – zpracování víceřadkových výsledků selectu
Indikátorové proměnné ●
●
●
●
Každou hostitelskou proměnnou je možné asociovat s indikátorovou proměnnou (2-byte integer – short), deklarace v deklarační částí jako hostitelské proměnné Asociovaná indikátorová proměnná indikuje,co je v hostitelské proměnné uloženo za hodnotu (detekce NULL hodnot, ořezání...) V SQL příkazech musí být uvozena dvojtečkou a musí následovat svou hostitelskou proměnnou Příklad:
:employee:ind_emp
kde employee je hostitelská a ind_emp je indikátorová
Indikátorové proměnné pokračování ●
Vstup
Hodnota indikátor. proměnné
Význam
-1
Hodnota hostitelské proměnné je interpretována jako NULL, skutečná hodnota se ignoruje
>=0
Bere se skutečná hodnota hostitelské proměnné
●
Výstup
Hodnota indikator. proměnné
Význam
-1
Ve sloupci je NULL, takže hodnota hostitelské proměnné je nedefinována
0
V hostitelské proměnné je celá hodnota
>0
Hodnota v hostitelské proměnné byla ořezána, v indikátorové je původní délka hodnoty, SQLCODE v SQLCA je nastaven na 0
-2
Hodnota v hostitelské byla ořezána, ale nelze určit původní velikost
Příklad použití indikátorových proměnných při insertu printf("Enter employee number or 0 if not available: "); scanf("%d", &emp_number); if (emp_number == 0) ind_empnum = -1; else ind_empnum = 0; EXEC SQL INSERT INTO emp (empno, sal) VALUES (:emp_number:ind_empnum, :salary);
Příklad použití indikátorových proměnných při selectu EXEC SQL SELECT ename, sal, comm INTO :emp_name, :salary, :commission:ind_comm FROM emp WHERE empno = :emp_number; if (ind_comm == -1) pay = salary; /* commission is NULL, ignore it */ else pay = salary + commission;
Příklad použití indikátorových proměnných při selectu II ●
Testování NULL ve WHERE klauzuli
EXEC SQL SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE :commission INDICATOR :ind_comm IS NULL ... ●
Porovnávání s možnou NULL hodnotou
EXEC SQL SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE (comm = :commission) OR ((comm IS NULL) AND :commission INDICATOR :ind_comm IS NULL));
Ošetření běhových chyb s použitím SQLCA ●
●
SQL Communications Area slouží k detekci chyb a zjištění změn ve statusech Struktura, jejíž položky plní Oracle při každém provedení SQL příkazu #include <sqlca.h> nebo EXEC SQL INCLUDE SQLCA; (mimo deklarační část, MODE=ORACLE)
●
Dva způsoby: –
Implicitní, pomocí klauzule WHENEVER
–
Explicitní, přímo pomocí SQLCA položek
sqlglm funkce ●
●
SQLCA obsahuje chybové zprávy o maximální délce 70 znaků Dlouhé nebo vnořené zprávy je možné získat funkcí sqlglm() void sqlglm(char *message_buffer, size_t *buffer_size, size_t *message_length);
kde message_length obsahuje aktuální délku chybové zprávy (max 512 znaků)
WHENEVER klauzule ●
●
●
Standardně jsou Oracle chyby a varování ignorovány a program pokračuje, pokud je to možné Automatické zachytávání chyb je lze zajistit pomocí WHENEVER klauzule (něco jako výjimky) Jakmile je detekována podmínka, je provedena akce EXEC SQL WHENEVER
;
Kde podmínkou může být –
SQLWARNING
–
SQLERROR
–
NOT FOUND (kritériu v SELECT neodpovídá žádný řádek, FETCH/SELECT INTO nevrátil žádný řádek)
WHENEVER akce ●
CONTINUE –
●
●
Defaultní akce
DO –
Provede se funkce, která chybu ošetří, po jejím skončení program pokračuje příkazem následujím po tom, kde nastala chyba
–
Je možné funkci předat parametry a použít její návratovou hodnotu
STOP –
Program skončí (volá se exit()) a je proveden rollback
WHENEVER akce – pokračování ●
DO BREAK –
●
DO CONTINUE –
●
Použití v cyklu, jakmile nastane podmínka, cyklus skončí Použití v cyklu, jakmile nastane podmínka program pokračuje další iterací
GOTO label –
Program pokračuje na místě, kde je příslušné návěští
–
Délka jména návěští je omezena (31 znaků)
Příklad – WHENEVER … DO EXEC SQL WHENEVER SQLERROR DO handle_insert_error("INSERT error"); EXEC SQL INSERT INTO emp (empno, ename, deptno) VALUES (:emp_number, :emp_name, :dept_number); ... handle_insert_error(char *stmt) { switch(sqlca.sqlcode) { case -1: /* duplicate key value */ ... break; case -1401: /* value too large */ ... break; default: /* do something here too */ ... break; } }
Platnost WHENEVER ●
●
Deklarativní příkaz, záleží na fyzické pozici, neřídí se logickým během programu Klauzule testuje všechny fyzicky následující příkazy, dokud není nahrazena jinou, testující stejnou podmínku
Příklad – platnost WHENEVER ●
●
První WHENEVER SQLERROR se aplikuje jen na CONNECT, pak je nahrazenou druhou Druhá WHENEVER SQLERROR se aplikuje na UPDATE i na DROP, ačkoli program step2 preskočí
step1: EXEC SQL WHENEVER SQLERROR STOP; EXEC SQL CONNECT :username IDENTIFIED BY :password; ... goto step3; step2: EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL UPDATE emp SET sal = sal * 1.10; ... step3: EXEC SQL DROP INDEX emp_index; ...
Doporučení ●
●
Pozor na nekonečné cykly při volání WHENEVER Návěští musí být dosažitelné ze všech odkazovaných GOTO
Kurzory ●
●
●
Pomáhají zpracovávat víceřádkové výsledky dotazů Umožňují procházet jednotlivé řádky (udržují si přehled, který řádek má být zpracován) Příkazy pro manipulaci s kurzory: –
DECLARE CURSOR – pojmenuje kurzor a sváže ho s dotazem
–
OPEN – vykoná dotaz a z výsledků dotazu vytvoří tzv. active set
–
FETCH – vrátí aktuální řádek ze setu, opakovaným voláním vrátí všechny
–
CLOSE – zavře kurzor, active set není definován
DECLARE CURSOR EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, empno, sal FROM emp WHERE deptno = :dept_number; ●
●
●
●
Je možné deklarovat tolik kurzorů, kolik je třeba (omezení je v proměnné MAXOPENCURSORS) Jméno kurzoru má globální platnost v rámci souboru SELECT příkaz nesmí obsahovat INTO, INTO je možné použít v příkazu FETCH Všechny příkazy k danému kurzoru musí být v jedné prekompilované jednotce
OPEN ●
Vykoná dotaz a vytvoří tzv. active set EXEC SQL OPEN emp_cursor;
●
●
●
OPEN vynuluje počet zpracovaných řádek, ale řádky samotné zatím viditelné nejsou, kurzor je před první řádkou active setu Po otevření se už změna v hodnotách hostitelských proměnných v dotazu neprojeví, je třeba znovu otevřít kurzor, aby se aktualizovali výsledky a tedy active set Při znovu otevírání je třeba buď kurzor nejprve zavřít (MODE=ANSI), nebo mít nastaveno MODE=ORACLE (default, rychlejší)
FETCH ●
Získá aktuální řádek z active setu a naplní výstupní hostitelské proměnné, tj. INTO a seznam hostitelských proměnných může být součástí FETCH příkazu
EXEC SQL FETCH emp_cursor INTO :emp_name, :emp_number, :salary; ●
●
●
Fetch je možný jen z otevřeného kurzoru První vykonání přesune kurzor na první řádek active setu – aktuální řádka Další vykonání fetch přesune kurzor na další řádek – nová aktuální řádka... aby se uživatel vrátil k již zpracované řádce, je nutné znovuotevřít kurzor
FETCH – pokračování ●
Je možné provést FETCH pokaždé s jimými výstupnímu hostitelskými proměnnými (ale stejného typu):
EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal FROM emp WHERE deptno = 20; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name1, :salary1; EXEC SQL FETCH emp_cursor INTO :emp_name2, :salary2; ... }
FETCH – pokračování II ●
●
●
●
Pokud je active set prázdný nebo už neobsahuje žádné další řádky vrací FETCH chybu: – chybový kód „no data found“ v SQLCA, nebo ve status proměnných SQLCODE či SQLSTATE, Při chybě „no data found “ hodnota výstupních proměnných není daná Obvykle se tato chyba zachycuje pomocí WHENEVER NOT FOUND Pokud chceme číst i pak, je třeba kurzor znovu otevřít
CLOSE EXEC SQL CLOSE emp_cursor; ●
●
●
Uvolní zdroje, které konkrétně se nastaví pomocí voleb HOLD_CURSOR a RELEASE_CURSOR Po uzavření není možné na kurzor použít fetch, active set není definován Příkazy COMMIT a ROLLBACK –
při MODE=ORACLE, zavřou kurzory v podmínce CURRENT OF, ostatní ponechají jak jsou.
–
při MODE=ANSI, se zavřou všechny explicitní kurzory, lze změnit nastavením CLOSE_ON_COMMIT na NO.
Příklad ... EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename,job FROM emp WHERE empno =:emp_number; EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :job_title; ... } ... EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; ...
Scrollable cursor ●
●
Umožnuje přistupovat k řádkům setu i jinak než sekvenčně Příklad:
EXEC SQL DECLARE emp_cursor SCROLL CURSOR FOR SELECT ename, sal FROM emp WHERE deptno=20; ... EXEC SQL OPEN emp_cursor; EXEC SQL FETCH LAST emp_cursor INTO :emp_name, :sal; EXEC SQL CLOSE emp_cursor; ...
Scrollable cursor – pokračování ●
Přehled FETCH příkazů: –
FETCH --náhodně
–
FETCH FIRST --první řádek setu
–
FETCH PRIOR --řádka předcházející aktuální
–
FETCH NEXT --řádka následující za aktuální
–
FETCH LAST --poslední řádka setu
–
FETCH CURRENT --aktuální řádka
–
FETCH RELATIVE n --n-tá řádka relativně vůči aktuální
–
FETCH ABSOLUTE n -- n-tá řádka setu
CURRENT OF klauzule ●
●
Klauzuli je možné použít v DELETE nebo UPDATE příkazu jako odkaz na aktuální řádku (vrácenou posledním FETCH) Pokud žádná aktuální řádka není, příkaz se neprovede a vrátí chybu
Příklad – CURRENT OF EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal FROM emp WHERE job = 'CLERK' FOR UPDATE OF sal; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO … for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :salary; ...
}
EXEC SQL UPDATE emp SET sal = :new_salary WHERE CURRENT OF emp_cursor;
FOR UPDATE OF klauzule ●
●
●
●
Používá se, když deklaruje kurzor, který je odkazován klauzulí CURRENT OF v UPDATE nebo DELETE příkazu Získá exkluzivní zámky na daných řádcích, vhodné například když upravuje hodnoty na základě stávajících Řádky jsou zamčeny při OPEN, po COMMITU/ROLLBACKU (ale ne k savepointu) jsou uvolněny, tj. pak už nelze udělat FETCH V případě použití CURRENT OF klauzule je použití FOR UPDATE OF volitelná, prekompilátor si ji kdyžtak doplní
Kompletní příklad 1 http://docs.oracle.com/cd/E11882_01/appdev.112/e10825 /pc_06sql.htm#i2240 #include <stdio.h> /* declare host variables */ char userid[12] = "SCOTT/TIGER"; char emp_name[10]; int emp_number; int dept_number; char temp[32]; void sql_error(); /* include the SQL Communications Area */ #include <sqlca.h>
Kompletní příklad 1 – pokračování main() { emp_number = 7499; /* handle errors */ EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); /* connect to Oracle */ EXEC SQL CONNECT :userid; printf("Connected.\n"); /* declare a cursor */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename FROM emp WHERE deptno = :dept_number; printf("Department number? "); gets(temp); dept_number = atoi(temp);
Kompletní příklad 1 – pokračování II /* open the cursor and identify the active set */ EXEC SQL OPEN emp_cursor; printf("Employee Name\n"); printf("-------------\n"); /* fetch and process data in a loop exit when no more data */ EXEC SQL WHENEVER NOT FOUND DO break; while (1) { EXEC SQL FETCH emp_cursor INTO :emp_name; printf("%s\n", emp_name); } EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; exit(0); } /* main */
Kompletní příklad 1 – pokračování III void sql_error(msg) char *msg; { char buf[500]; int buflen, msglen; EXEC SQL WHENEVER SQLERROR CONTINUE; /* to avoid infinite loop when rollback fails*/ EXEC SQL ROLLBACK WORK RELEASE;
}
buflen = sizeof (buf); sqlglm(buf, &buflen, &msglen); /* gets full msg*/ printf("%s\n", msg); printf("%*.s\n", msglen, buf); exit(1);
Kompletní příklad 2 http://docs.oracle.com/cd/E11882_01/appdev.112/e10825/pc_09err.htm#sthref1288 #include <sqlca.h> #include <stdio.h> main() { char *uid = "scott/tiger"; struct { char ename[12]; float sal; float comm; } emp; /* Trap any connection error that might occur. */ EXEC SQL WHENEVER SQLERROR GOTO whoops; EXEC SQL CONNECT :uid; EXEC SQL DECLARE c CURSOR FOR SELECT ename, sal, comm FROM EMP ORDER BY ENAME ASC; EXEC SQL OPEN c;
Kompletní příklad 2 – pokračování I /* Set up 'BREAK' condition to exit the loop. */ EXEC SQL WHENEVER NOT FOUND DO BREAK; /* The DO CONTINUE makes the loop start at the next iteration when an error occurs.*/ EXEC SQL WHENEVER SQLERROR DO CONTINUE; while (1) { EXEC SQL FETCH c INTO :emp; /* An ORA-1405 would cause the 'continue' to occur. So only employees with non-NULL commissions will be displayed. */ emp.comm); }
printf("%s
%7.2f
%9.2f\n", emp.ename, emp.sal,
Kompletní příklad 2 – pokračování II /* This 'CONTINUE' shuts off the 'DO CONTINUE' allowing the program to proceed if any further errors do occur, specifically, with the CLOSE */ EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL CLOSE c; exit(EXIT_SUCCESS); whoops: printf("%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); exit(EXIT_FAILURE); }
Zdroje ●
●
Pro*C/C++ Programmer's Guide 11g Release 2 (11.2) [online], kap. 6 Embedded SQL, dostupné na: http://docs.oracle.com/cd/E11882_01/appdev.112/e10825/pc_06sql.htm#g19457 Pro*C/C++ Programmer's Guide 11g Release 2 (11.2) [online], kap. 9 Handling Runtime Errors, dostupné na: http://docs.oracle.com/cd/E11882_01/appdev.112/e10825/pc_09err.htm#g35612