WebZdarma.cz

Úvod do dynamického SQL [Dynamic SQL]

Co to je?

Většina databázových aplikací dělá velmi konkrétní úkony. Například na základě vstupního čísla zaměstnance a nového platu upraví tabulku s informacemi o zaměstnancích dle daných požadavků. Nicméně existuje i třída aplikací, které musí provádět velmi různé SQL dotazy o jejichž konkrétním tvaru se rozhoduje až při běhu programu. Například obecný generátor výstupních sestav musí sestavovat různé SELECTy pro různé výstupy, jež jsou po něm požadovány. V takovém případě ještě není v době kompilace přesné znění dotazu známo a dotazy se budou pravděpodobně spuštění od spuštění líšit - nazýváme je dynamické SQL.

Na rozdíl od statických dotazů, dynamické nejsou obsaženy ve zdrojovém kódu programu. Objevují se ve vstupních řetězcích, uloženy v souboru, nebo jsou programem sestavovány až za běhu.

Výhody a nevýhody

Programy využívající dynamické SQL dotazy jsou obvykle mnohem univerzálnější než-li programy s dotazy statickými, pevně zadrátovanými v programu. Dynamické dotazy mohou být sestavovány interaktivně na základě vstupu uživatele, který zná jen minimum, nebo vůbec nic z jazyka SQL.

Program může například jednoduše požádat uživatele o dopnění WHERE podmínky, která bude využita v SELECT, UPDATE či DELTE klauzuli. Komplexnější program může uživateli nabídnout menu s mnoha možnými SQL operacemi, tabulkami či sloupci. Dynamické SQL tak umožnuje psát velmi flexibilní programy.

Nicméně jako všude, i zde se dají nalézt jistá omezení či nevýhody. Někdy je totiž potřeba opravdu složitých konstrukcí v programu a tedy i složitějšího zpracovávání za běhu.

Kdy dynamické SQL využít?

V praxi bude statické SQL dostačovat ve velké většině programů, nicméně občas je požadována flexibilita, která naráží na omezení statických dotazů. Využití je možno doporučit například pokud není v době kompilace známo:

Požadavky dynamického SQL

K reprezentaci dynamického SQL musí řetězec obsahovat korektní SQL dotaz, nesmí však obsahovat EXEC SQL klauzuli, ukončovač příkazu, nebo některý z následujícíh příkazů využívaných v embedded SQL:

Ve většině případu můžou řetězce obsahovat také "falešné" (dummy) hostitelské proměnné, které pouze "drží místo" pravým proměnným. Vzhledem k tomu, že falešné hostitelské proměnné pouze "drží místo", není třeba je deklarovat, a je možno je pojmenovávat libovolně. Například mezi následujícími dvěma řetězci nebude v dynamickém dotazu Oracle dělat žádný rozdíl:

'DELETE FROM EMP WHERE MGR = :mgr_number AND JOB = :job_title' 
'DELETE FROM EMP WHERE MGR = :m AND JOB = :j'

Zpracovávání dynamických SQL dotazů

V typickém případě je text SQL dotazu vyžádán na vstupu od uživatele spolu s potřebnými hodnotami hostitelských proměnných. Oracle pak rozparsuje SQL dotaz aby ověřil dodržení syntaktických pravidel, dále pak jsou hostitelské proměnné připojeny (bind) k SQL dotazu. Což pro Oracle znamená získání jejich adres tak, aby mohly být načteny jejich hodnoty. Poté již je "spuštěn" samotný dotaz a jsou provedny odpovídající akce nad databází. Takovéto dynamické dotazy samozřejmě mohou být spouštěny opakovaně, s měnícími se hodnotami hostitelských proměnných.

Metody používání dynamických dotazů

V této kapitolce se letmo podíváme na 4 možné způsoby definice dynamických SQL dotazů v Oracle. Nastíníme jejich rozdíly, výhody a nevýhody. Jak bude vidět, metody které budeme popisovat postupně nabudou na své složitosti a obecnosti (což znamená že všechny dotazy které můžeme vytvořit pomoci metody první hravě zvládneme také metodou čtvrtou, která ale navíc umí mnohé další). První stručný přehled bychom mohli shrnout do následujícího seznamu:

Abychom zbytečně nepředbíhali, podíváme se důkladněji pouze na první zmíněnou metodu, ostatní naznačíme jen v jednoduchých příkladech:

Metoda 1 - po přijetí řetězce s dotazem je ihned příkaz spuštěn pomocí EXECUTE IMMEDIATE. Příkaz nesmí být typu SELECT a nesmí obsahovat žádné hositelské proměnné. V této metodě je každý příkaz před každým spuštěním znova parsován. Příkladem by mohly být následující řetězce:

'DELETE FROM EMP WHERE DEPTNO = 20' 
'GRANT SELECT ON EMP TO scott'

Metoda 2 - zde jsou po přijetí dotazu využity metody PREPARE a EXECUTE. SQL příkaz opět nesmí být typu SELECT, počet figurujících proměnných musí být znám už v době precompilace. V programu jsou takovéto příkazy parsovány pouze jednou, a mohou být s různými hodnotami hostitelských proměnných spuštěný mnoho krát. Příkladem stringů spadajících do této kategorie mohou být třeba:

'INSERT INTO EMP (ENAME, JOB) VALUES (:emp_name, :job_title)' 
'DELETE FROM EMP WHERE EMPNO = :emp_number'

Metoda 3 - metoda dovoluje přijmout či sestavit SQL dotaz a zpracovat jej pomocí PREPARE spolu s kurzorovými příkazy DECLARE, OPEN, FETCH a CLOSE. Počet položek v SELECTu i počet míst pro hostitelské proměnné a datové typy vstupních hodnot musí být známý už při precompilaci. Příkladem můžou být:

'SELECT DEPTNO, MIN(SAL), MAX(SAL) FROM EMP GROUP BY DEPTNO' 
'SELECT ENAME, EMPNO FROM EMP WHERE DEPTNO = :dept_number' 

Metoda 4 - zde není není třeba znát počet položek SELECTu, počet a dokonce ani datové typy vstupních proměnných dříve než během run-time. Do dané kategorie patří kupříkladu následující řetězce:

'INSERT INTO EMP (<unknown>) VALUES (<unknown>)' 
'SELECT <unknown> FROM EMP WHERE DEPTNO = 20'

První metoda dynamického SQL

Tento způsob tvorby dynamického dotazu je ze všech čtyřech zmíněných nejjednodušší. Jediný výsledek který z dotazu můžeme dostat je totiž oznámení o úspěchu (success) či selhání (failure). Nejprve pár příkladů:

'DELETE FROM table_name WHERE column_name = constant' 
'CREATE TABLE table_name ...' 
'DROP INDEX index_name' 
'UPDATE table_name SET column_name = constant' 
'GRANT SELECT ON table_name TO username' 
'REVOKE RESOURCE FROM username' 

Pomocí první metody je SQL příkaz pouze zparsován a okamžitě spuštěn pomocí příkazu EXECUTE IMMEDIATE, který je následován řetězcem obsahujícím SQL příkaz ke spuštění (jež nemůže být SELECT). Syntaxe EXECUTE IMMEDIATE je následující:

EXEC SQL EXECUTE IMMEDIATE { :host_string | string_literal };

V následujícím jednoduchém příkladě načteme do proměnné dyn_stmt uživatelův SQL příkaz, který následně provedeme:

char dyn_stmt[132]; 
... 
for (;;) 
{ 
    printf("Vlozte SQL prikaz: "); 
    gets(dyn_stmt); 
    if (*dyn_stmt == '\0') {
        break; 
    }
    /* dyn_stmt ted obsahuje text SQL prikazu */ 
    EXEC SQL EXECUTE IMMEDIATE :dyn_stmt; 
} 
... 

Podobně je možno použít v EXECUTE IMMEDIATE také řetězcové konstany:

EXEC SQL EXECUTE IMMEDIATE 'REVOKE RESOURCE FROM MILLER';

Vzhledem k tomu, že první metoda před každým spuštěním parsuje celý vstupující SQL příkaz, její nejlepší uplanění je v dotazech které jsou provedeny pouze jednou.

Příklad první metody

Následující jednoduchý program využije dynamické SQL k vytvoření tabulky, vložení řádku, jeho potvrzení a zahození tabulky:

#include <stdio.h>
#include <string.h>
 
#include <sqlca.h>
 
/* Pridame ORACLE Communications Area - strukturu skrz niz
 * ORACLE sprostredkovava dodatecne behove stavove informace
 * dostupne v programu.
 */
#include <oraca.h>
 
/* Zapneme ORACA=YES pro zpristupneni ORACLE Comm. Area v programu */
EXEC ORACLE OPTION (ORACA=YES);
 
/* Nastaveni RELEASE_CURSOR=YES rekne Pro*Ccku, aby byly uvolnovany
 * kurzory ve vlozenem SQL ihned po spusteni. Coz zaruce, ze
 * ORACLE nebude drzet parsovaci zamky na tabulkach
 * po operacich manipulujicich s daty, takze nasledujici
 * operace definujici data na techto tabulkach nezpusobi parse-lock error.
 */
EXEC ORACLE OPTION (RELEASE_CURSOR=YES);
 
void dyn_error();
 
main()
{
    /* Nadefinujeme hostitelske promenne. */
    char    *username = "SCOTT";
    char    *password = "TIGER";
    char    *dynstmt1;
    char     dynstmt2[10];
    VARCHAR  dynstmt3[80];
 
    /* Kdykoliv nastane chyba, zavolame dyn_error(). */
    EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error:");
 
    /* Pokud nastane chyba, ulozime text aktualniho SQL dotazu do ORACA. */
    oraca.orastxtf = ORASTFERR;
 
    /* Pripojime se k databazi. */
    EXEC SQL CONNECT :username IDENTIFIED BY :password;
    puts("\nConnected to ORACLE.\n");
 
    /* Spustime konstantni retezec s dotazem k vytvoreni tabulky. 
     * V tomto pripade se vlastne ani nejedna o dynamicke SQL,
     * nebot program nezjistuje hodnotu vyrazu az za behu.
     */
    puts("CREATE TABLE dyn1 (col1 VARCHAR2(4))");
 
    EXEC SQL EXECUTE IMMEDIATE
        "CREATE TABLE dyn1 (col1 VARCHAR2(4))";
 
    /* Spustime pridani radku. Retezec musi byt klasicky
     * null-terminated. Tento priklad pouziti jiz se dynamickym
     * SQL nazvat tad, nebot SQL prikaz je retezcova promenna,
     * jejiz hodnotu muze program vyhodnotit az behem spusteni.
     */
    dynstmt1 = "INSERT INTO DYN1 values ('TEST')";
    puts(dynstmt1);
 
    EXEC SQL EXECUTE IMMEDIATE :dynstmt1;
 
    /* SQL prikaz k potvrzeni vlozeneho radku.
     * Nevyuzita cast pole je doplnena mezerami,
     * neni zadouci na konec pridavat nulovy znak
     * (jak tomu bylo v predchozim pripade).
     */
    strncpy(dynstmt2, "COMMIT    ", 10);
    printf("%.10s\n", dynstmt2);
 
    EXEC SQL EXECUTE IMMEDIATE :dynstmt2;
 
    /* Nakonec spustime prikaz k zahozeni tabulky,
     * ulozeny ve VARCHARu. Je samozrejme potreba
     * take nastavit delku pole .arr, jez je ulozena
     * v .len
     */
    strcpy(dynstmt3.arr, "DROP TABLE DYN1");
    dynstmt3.len = strlen(dynstmt3.arr);
    puts((char *) dynstmt3.arr);
 
    EXEC SQL EXECUTE IMMEDIATE :dynstmt3;
 
    /* Potvrdime zmeny a odpojime se od databaze. */
    EXEC SQL COMMIT RELEASE;
 
    return 0;
}

 

 

Druhá metoda dynamického SQL

Druhá metoda dynamického SQL pracuje ve dvou krocích – nejdříve příkaz připraví pomocí PREPARE a následně provede pomocí EXECUTE. SQL dotaz může obsahovat tzv. Placeholders, za které se při provádění dotazu dosadí skutečné proměnné. Příkaz tak lze připravit jen jednou a provádět opakovaně s různými vstupními hodnotami. Co víc, po COMMITu či ROLLBACKu není třeba příkaz znovu připravit (výjimkou je odhlášení a přihlášení k databázi)

 

Syntaxe PREPARE:

EXEC SQL PREPARE statement_name 
     FROM { :host_string | string_literal }; 
 
 

PREPARE rozparsuje SQL příkaz a pojmenuje ho.

 

statement_name  je jen identifikátor použitý prekompilátorem, není to hostitelská proměnná a tudíž se nedefinuje v declare sekci.

 

Syntaxe EXECUTE:

EXEC SQL EXECUTE statement_name [USING host_variable_list];
 

kde host_variable_list má syntaxi:

:host_variable1[:indicator1] [, host_variable2[:indicator2], ...] 
 

EXECUTE provádí rozparsovaný dotaz a dosazuje za placeholdery dané proměnné

 

Zde je příklad na použití placeholderu (:n):

... 
int emp_number    INTEGER; 
char delete_stmt[120], search_cond[40];; 
... 
strcpy(delete_stmt, "DELETE FROM EMP WHERE EMPNO = :n AND "); 
printf("Complete the following statement's search condition--\n"); 
printf("%s\n", delete_stmt); 
gets(search_cond); 
strcat(delete_stmt, search_cond); 
 
EXEC SQL PREPARE sql_stmt FROM :delete_stmt; 
for (;;) 
{ 
 
     printf("Enter employee number: "); 
    gets(temp);
    emp_number = atoi(temp); 
    if (emp_number == 0) 
        break; 
    EXEC SQL EXECUTE sql_stmt USING :emp_number; 
} 
... 
 

Druhá metoda požaduje, aby byl typ vstupní proměnné znám již při prekompilaci. Jak je vidět v předchozím příkladě, :n je deklarováno jako int.

Klauzule USING

Při EXECUTE se všechny placeholdery z rozparsovaného dotazu nahradí odpovídajícími proměnnými z klauzule USING, přičemž pořadí I počet musí odpovídat. Jestliže se má některá proměnná dosadit dvakrát, musí se take dvakrát oběvit v klauzuli using. Jména placeholderů a proměnných si odpovídat nemusejí. Pokud je kterákoliv z proměnných v USING pole, musí být pole všechny. NULL hodnoty se specifikůjí pomocí indikátorů.

 

Příklad použití metody 2

Následující příklad vloží pomocí metody 2 dynamického SQL dva řádky do tabulky EMP, načež je smaže.

/*
 *  This program uses dynamic SQL Method 2 to insert two rows into
 *  the EMP table, then delete them.
 */
 
#include <stdio.h>
#include <string.h>
 
#define USERNAME "SCOTT"
#define PASSWORD "TIGER"
 
/* Include the SQL Communications Area, a structure through
 * which ORACLE makes runtime status information such as error
 * codes, warning flags, and diagnostic text available to the
 * program.
 */
#include <sqlca.h>
 
/* Include the ORACLE Communications Area, a structure through
 * which ORACLE makes additional runtime status information
 * available to the program.
 */
#include <oraca.h>
 
/* The ORACA=YES option must be specified to enable use of
 * the ORACA.
 */
EXEC ORACLE OPTION (ORACA=YES);
 
char    *username = USERNAME;
char    *password = PASSWORD;
VARCHAR  dynstmt[80];
int      empno   = 1234;
int      deptno1 = 97;
int      deptno2 = 99;
 
 
/* Handle SQL runtime errors. */
void dyn_error();
 
 
main()
{
/* Call dyn_error() whenever an error occurs
 * processing an embedded SQL statement.
 */
    EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error");
 
/* Save text of current SQL statement in the ORACA if an
 * error occurs.
 */
    oraca.orastxtf = ORASTFERR;
 
/* Connect to Oracle. */
 
    EXEC SQL CONNECT :username IDENTIFIED BY :password;
    puts("\nConnected to Oracle.\n");
 
/* Assign a SQL statement to the VARCHAR dynstmt.  Both
 * the array and the length parts must be set properly.
 * Note that the statement contains two host-variable
 * placeholders, v1 and v2, for which actual input
 * host variables must be supplied at EXECUTE time.
 */
    strcpy(dynstmt.arr,
        "INSERT INTO EMP (EMPNO, DEPTNO) VALUES (:v1, :v2)");
    dynstmt.len = strlen(dynstmt.arr);
 
/* Display the SQL statement and its current input host
 * variables.
 */
    puts((char *) dynstmt.arr);
    printf("   v1 = %d,  v2 = %d\n", empno, deptno1);
 
/* The PREPARE statement associates a statement name with
 * a string containing a SQL statement.  The statement name
 * is a SQL identifier, not a host variable, and therefore
 * does not appear in the Declare Section.
 
 * A single statement name can be PREPAREd more than once,
 * optionally FROM a different string variable.
 */
    EXEC SQL PREPARE S FROM :dynstmt;
 
/* The EXECUTE statement executes a PREPAREd SQL statement
 * USING the specified input host variables, which are
 * substituted positionally for placeholders in the
 * PREPAREd statement.  For each occurrence of a
 * placeholder in the statement there must be a variable
 * in the USING clause.  That is, if a placeholder occurs
 * multiple times in the statement, the corresponding
 * variable must appear multiple times in the USING clause.
 * The USING clause can be omitted only if the statement
 * contains no placeholders.
 *
 * A single PREPAREd statement can be EXECUTEd more
 * than once, optionally USING different input host
 * variables.
 */
    EXEC SQL EXECUTE S USING :empno, :deptno1;
 
/* Increment empno and display new input host variables. */
 
    empno++;
    printf("   v1 = %d,  v2 = %d\n", empno, deptno2);
 
/* ReEXECUTE S to insert the new value of empno and a
 * different input host variable, deptno2.
 * A rePREPARE is unnecessary.
 */
    EXEC SQL EXECUTE S USING :empno, :deptno2;
 
/* Assign a new value to dynstmt. */
 
    strcpy(dynstmt.arr,
        "DELETE FROM EMP WHERE DEPTNO = :v1 OR DEPTNO = :v2");
    dynstmt.len = strlen(dynstmt.arr);
 
/* Display the new SQL statement and its current input host
 * variables.
 */
    puts((char *) dynstmt.arr);
    printf("   v1 = %d,    v2 = %d\n", deptno1, deptno2);
 
/* RePREPARE S FROM the new dynstmt. */
 
    EXEC SQL PREPARE S FROM :dynstmt;
 
/* EXECUTE the new S to delete the two rows previously
 * inserted.
 */
    EXEC SQL EXECUTE S USING :deptno1, :deptno2;
 
/* Commit any pending changes and disconnect from Oracle. */
 
    EXEC SQL COMMIT RELEASE;
    puts("\nHave a good day!\n");
    exit(0);
}
 
 
void 
dyn_error(msg)
char *msg;
{
/* This is the ORACLE error handler.
 * Print diagnostic text containing error message,
 * current SQL statement, and location of error.
 */
    printf("\n%s", msg);
    printf("\n%.*s\n",
        sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
    printf("in \"%.*s...\"\n",
        oraca.orastxt.orastxtl, oraca.orastxt.orastxtc);
    printf("on line %d of %.*s.\n\n",
        oraca.oraslnr, oraca.orasfnm.orasfnml,
        oraca.orasfnm.orasfnmc);
 
/* Disable ORACLE error checking to avoid an infinite loop
 * should another error occur within this routine.
 */
    EXEC SQL WHENEVER SQLERROR CONTINUE;
 
/* Roll back any pending changes and 
 * disconnect from Oracle.
 */
    EXEC SQL ROLLBACK RELEASE;
    exit(1);
}
 

Třetí metoda dynamického SQL

Třetí metoda je velmi podobná té druhé, jen přidává možnost práce s kurzory, a tudíž možnost získání dat z databáze. Při použití třetí metody je potřeba znát jen počet návratových hodnot a hostitelských proměnných. Databázové objekty (tabulky, sloupce…) a klauzule jako WHERE, GROUP BY či ORDER BY mohou být specifikovány až za běhu.

 

Třetí metoda používá následující pořadí příkazů:

 
PREPARE statement_name FROM { :host_string | string_literal }; 
DECLARE cursor_name CURSOR FOR statement_name; 
OPEN cursor_name [USING host_variable_list]; 
FETCH cursor_name INTO host_variable_list; 
CLOSE cursor_name; 
 

 

PREPARE

PREPARE se chová naprosto stejně jako v metodě 2 .V následujícím příkladě PREPARE parsuje dotaze uložený v proměnné select_stmt a pojmenovává ho sql_stmt:

 
char select_stmt[132] =
 "SELECT MGR, JOB FROM EMP WHERE SAL < :salary";
EXEC SQL PREPARE sql_stmt FROM :select_stmt;
 

DECLARE

DECLARE definuje kurzor a přiřadí ho k zadanému dotazu. Pokračujíc v příkladu, DECLARE vytváří  kurzor emp_cursor  a asociuje ho s sql_stmt :

 

EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; 
 

Identifikátory sql_stmt a emp_cursor nejsou hostitelské proměnné, ale musí být unikátní.

OPEN

OPEN alokuje Oracle kurzor, připojí vstupní proměnné, provede dotaz a nastaví kurzor na první řádku aktivní množiny. Placeholdery připraveného dotazu jsou nahrazeny odpovídajícími proměnnými.

 

V následujícím příkladě OPEN allokuje emp_cursor a dosadí hostitelskou proměnnou do WHERE klauzule (viz výše):

EXEC SQL OPEN emp_cursor USING :salary;
 

FETCH

FETCH vrací řádek z aktivní množiny, hodnoty z řádku přiřadí do odpovídajících hostitelských proměnných z klauzule INTO a posune kurzor na další řádek. Pokud už žádný další není, vrátí Oracle error "no data found" do sqlca.sqlcode.

 

EXEC SQL FETCH emp_cursor INTO :mgr_number, :job_title; 
 

CLOSE

CLOSE kurzor uzavře, není již tedy možno z něj FETCHovat.

 

EXEC SQL CLOSE emp_cursor; 

 

Příklad použití metody 3

Následující program získá jména všech zaměstnanců v zadaném oddělení z tabulky EMP.

 

/*
 *  This program uses dynamic SQL Method 3 to retrieve the names
 *  of all employees in a given department from the EMP table.
 */
 
#include <stdio.h>
#include <string.h>
 
#define USERNAME "SCOTT"
#define PASSWORD "TIGER"
 
/* Include the SQL Communications Area, a structure through
 * which ORACLE makes runtime status information such as error
 * codes, warning flags, and diagnostic text available to the
 * program. Also include the ORACA.
 */
#include <sqlca.h>
#include <oraca.h>
 
/* The ORACA=YES option must be specified to enable use of
 * the ORACA.
 */
EXEC ORACLE OPTION (ORACA=YES);
 
char    *username = USERNAME;
char    *password = PASSWORD;
VARCHAR  dynstmt[80];
VARCHAR  ename[10];
int      deptno = 10;
 
void dyn_error();
 
 
main()
{
/* Call dyn_error() function on any error in
 * an embedded SQL statement.
 */
    EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error");
 
/* Save text of SQL current statement in the ORACA if an
 * error occurs.
 */
    oraca.orastxtf = ORASTFERR;
 
/* Connect to Oracle. */
 
    EXEC SQL CONNECT :username IDENTIFIED BY :password;
    puts("\nConnected to Oracle.\n");
 
/* Assign a SQL query to the VARCHAR dynstmt.  Both the
 * array and the length parts must be set properly.  Note
 * that the query contains one host-variable placeholder,
 * v1, for which an actual input host variable must be
 * supplied at OPEN time.
 */
    strcpy(dynstmt.arr,
        "SELECT ename FROM emp WHERE deptno = :v1");
    dynstmt.len = strlen(dynstmt.arr);
 
/* Display the SQL statement and its current input host
 * variable.
 */
    puts((char *) dynstmt.arr);
    printf("   v1 = %d\n", deptno);
    printf("\nEmployee\n");
    printf("--------\n");
 
/* The PREPARE statement associates a statement name with
 * a string containing a SELECT statement.  The statement
 * name is a SQL identifier, not a host variable, and
 * therefore does not appear in the Declare Section.
 
 * A single statement name can be PREPAREd more than once,
 * optionally FROM a different string variable.
 */
    EXEC SQL PREPARE S FROM :dynstmt;
 
/* The DECLARE statement associates a cursor with a
 * PREPAREd statement.  The cursor name, like the statement
 * name, does not appear in the Declare Section.
 
 * A single cursor name cannot be DECLAREd more than once.
 */
    EXEC SQL DECLARE C CURSOR FOR S;
 
/* The OPEN statement evaluates the active set of the
 * PREPAREd query USING the specified input host variables,
 * which are substituted positionally for placeholders in
 * the PREPAREd query.  For each occurrence of a
 * placeholder in the statement there must be a variable
 * in the USING clause.  That is, if a placeholder occurs
 * multiple times in the statement, the corresponding
 * variable must appear multiple times in the USING clause.
 
 * The USING clause can be omitted only if the statement
 * contains no placeholders.  OPEN places the cursor at the
 * first row of the active set in preparation for a FETCH.
 
 * A single DECLAREd cursor can be OPENed more than once,
 * optionally USING different input host variables.
 */
    EXEC SQL OPEN C USING :deptno;
 
/* Break the loop when all data have been retrieved. */
 
    EXEC SQL WHENEVER NOT FOUND DO break;
 
/* Loop until the NOT FOUND condition is detected. */
 
    for (;;)
    {
/* The FETCH statement places the select list of the
 * current row into the variables specified by the INTO
 * clause, then advances the cursor to the next row.  If
 * there are more select-list fields than output host
 * variables, the extra fields will not be returned.
 * Specifying more output host variables than select-list
 * fields results in an ORACLE error.
 */
        EXEC SQL FETCH C INTO :ename;
 
/* Null-terminate the array before output. */
        ename.arr[ename.len] = '\0';
        puts((char *) ename.arr);
        }
 
/* Print the cumulative number of rows processed by the
 * current SQL statement.
 */
    printf("\nQuery returned %d row%s.\n\n", sqlca.sqlerrd[2],
        (sqlca.sqlerrd[2] == 1) ? "" : "s");
 
/* The CLOSE statement releases resources associated with
 * the cursor.
 */
    EXEC SQL CLOSE C;
 
/* Commit any pending changes and disconnect from Oracle. */
    EXEC SQL COMMIT RELEASE;
    puts("Sayonara.\n");
    exit(0);
}
 
void
dyn_error(msg)
char *msg;
{
    printf("\n%s", msg);
    sqlca.sqlerrm.sqlerrmc[sqlca.sqlerrm.sqlerrml] = '\0';
    oraca.orastxt.orastxtc[oraca.orastxt.orastxtl] = '\0';
    oraca.orasfnm.orasfnmc[oraca.orasfnm.orasfnml] = '\0';
    printf("\n%s\n", sqlca.sqlerrm.sqlerrmc);
    printf("in \"%s...\"\n", oraca.orastxt.orastxtc);
    printf("on line %d of %s.\n\n", oraca.oraslnr,
       oraca.orasfnm.orasfnmc);
 
/* Disable ORACLE error checking to avoid an infinite loop
 * should another error occur within this routine.
 */
    EXEC SQL WHENEVER SQLERROR CONTINUE;
 
/* Release resources associated with the cursor. */
    EXEC SQL CLOSE C;
 
/* Roll back any pending changes and disconnect from Oracle. */  
    EXEC SQL ROLLBACK RELEASE;
    exit(1);

}