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á
Vstup Výstup Indikátorové proměnné - pokračování 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é 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 <condition> <action>; 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)
CONTINUE DO Defaultní akce WHENEVER akce 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 Použití v cyklu, jakmile nastane podmínka, cyklus skončí DO CONTINUE 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. */ printf("%s %7.2f %9.2f\n", emp.ename, emp.sal, emp.comm); }
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