Prikaži sadržaj

Programiranje baza podataka

4. Aplikacije sa dinamičkim SQL naredbama

Ova stranica je pod konstrukcijom!
Ako pronađete grešku ili propust, molimo Vas da nam skrenete pažnju otvaranjem primedbe na zvaničnom GitHub repozitorijumu.

Do sada smo konstruisali aplikacije sa ugnežđenim SQL-om koje su koristile statičke SQL naredbe. Da se podsetimo, u pitanju su bile naredbe čiji je plan izvršavanja moguće izračunati u fazi pretprocesiranja. Ipak, kreiranje statičkih SQL aplikacija nam nije dovoljno ukoliko su nam neki elementi SQL naredbi nepoznati u fazi prevođenja programa. Štaviše, nekada je potrebno konstruisati aplikaciju u kojoj nijedna SQL naredba nije unapred poznata (sa izuzetkom naredba povezivanja na bazu podataka, prekidanja konekcije i sl.).

Dizajniranje složenijih SQL aplikacija zahteva nešto moćnije alate za rad koji se oslanjaju na rad sa dinamičkim SQL naredbama, čime se kreiraju dinamičke SQL aplikacije. Samim tim što je programiranje dinamičkih SQL aplikacija moćnije, to je i složenije jer, pored velikog broja novih načina za pisanje aplikacija, otvara i nove teme, pre svega poređenje složenosti, performansi i upotrebljivosti u odnosu na statičke SQL aplikacije. Međutim, u ovom poglavlju, mi ćemo se baviti isključivo programiranjem dinamičkih SQL aplikacija, ne i pitanjima njihovog razvoja.

Pre nego što započnemo sa poglavljem, napomenimo da ukoliko se koriste struktuirani podaci ili podaci koji su nekog od tipa LOB (BLOB, CLOB, DBLOB, itd.), onda je za neke od naredbi potrebno izvršiti dodatna procesiranja. Zbog toga ćemo pretpostaviti da naše aplikacije neće baratati takvim podacima.

4.1 Naredbe za ugnežđavanje dinamičkih SQL naredbi

Da bismo mogli da izvršimo SQL naredbu dinamički, prvo je neophodno skladištiti je u matičnu promenljivu čiji je tip niska. Dinamička SQL naredba podrazumeva da matična promenljiva-niska sadrži tekstualnu formu SQL naredbe. Ova naredba, koja je zadata kao tekst, biće procesirana dinamički, odnosno, tek u fazi izvršavanja programa.

Naredna slika prikazuje deo programskog koda koji procesira dinamičku SQL naredbu SELECT. Na početku, dinamička SQL naredba je zadata kao niska. Prvi korak se sastoji u procesiranju tog tekstualnog oblika SQL naredbe. Rezultat ove naredbe je pripremljena SQL naredba, odnosno, njen izvršivi oblik. Dodatno, moguće je ovom naredbom upisati informacije o samoj dinamičkoj SQL naredbi u odgovarajuće strukture podataka. Nakon toga, drugi korak se sastoji u samom izvršavanju pripremljene naredbe. Prilikom izvršavanja se informacije koje su bile nepoznate zamenjuju konkretnim vrednostima, naravno, ako je dinamička SQL naredba imala nepoznate informacije.

"Proces izvršavanja dinamičkih SQL naredbi u dva koraka"

Tekstualni oblik naredbe neće biti procesiran prilikom prekompilacije aplikacije. Zapravo, sam tekstualni sadrži ne mora ni da postoji u fazi pretprocesiranja. Umesto toga, o dinamičkoj SQL naredbi možemo razmišljati kao matičnoj promenljivoj tokom faze prekompilacije na koju će se referisati prilikom faze izvršavanja.

Sadržaj dinamičkih SQL naredbi odgovara istoj sintaksi kao i za statičke SQL naredbe. Ipak, postoje neki izuzeci nametnuti od strane DB2 SUBP:

Da bismo tekstualni oblik SQL naredbe transformisali u njegov izvršivi oblik, potrebno je izvršiti odgovarajuće korake, odnosno, naredbe za podršku dinamičkom SQL-u. Ove naredbe operišu nad matičnim promenljivama tako što procesiraju tekstualni oblik naredbe, proveravajući sintaksu naredbe i druga pravila koja ta naredba mora da zadovolji. Ovih naredbi ima više, a mi ćemo prikazati svaku od njih u nastavku poglavlja. Za sada, dajmo kratak opis naredbi kako bismo ih imali na umu tokom njihove detaljnije diskusije:

4.1.1 Prikazivanje detalja greške u programima

S obzirom da se dinamičke SQL naredbe procesiraju tek u fazi izvršavanja programa, Db2 pretprocesor nije u stanju da analizira upit i da nam u fazi prevođenja javi ukoliko je došlo do greške. Ovo nam može predstavljati problem pošto naša funkcija za obradu greške samo ispisuje njen kod. Značilo bi nam da imamo neke dodatne informacije ispisane pored samog koda.

Srećom po nas, ovo je moguće i veoma je jednostavno za uraditi. Ukoliko uključimo zaglavlje sql.h, na raspolaganju će nam biti funkcija sqlaintp, koju možemo da iskoristimo da dohvatimo tekstualni opis greške koja se dogodila. Ova funkcija ima naredna četiri argumenta:

  1. char ∗pBuffer - Karakterni bafer u koji će biti smešten tekstualni opis greške. Ukoliko je veličina bafera manja nego što je dužina opisa, opis će biti skraćen na veličinu bafera i biće postavljena terminirajuća nula na kraju bafera.
  2. short BufferSize - Veličina bafera koji se prosleđuje kao prvi argument.
  3. short LineWidth - Najveća veličina svake linije u tekstu opisa. Linije će biti razbijene odgovarajućim karakterom za novi red tako da reči u datoj liniji ostanu cele. Ukoliko prosledimo vrednost 0, poruka neće sadržati karaktere za novi red.
  4. struct sqlca ∗pSqlca - Pokazivač na strukturu sqlca na osnovu koje će funkcija zaključiti do koje greške je došlo. S obzirom da mi koristimo globalnu strukturu koja se dobija uključivanjem zaglavlja SQLCA Db2 pretprocesorskom direktivom INCLUDE, a koja se koristi za dohvatanje informacije o grešci, onda ćemo ovde prosleđivati upravo tu globalno-definisanu strukturu.

Naša funkcija za obradu greške će sada izgledati:

#include <sql.h> // Za funkciju sqlaintp()

// Podsetnik: Naredna Db2 pretprocesorka direktiva ce ucitati zaglavlje `sqlca.h` 
// i definisati globalnu `sqlca` strukturu koja se koristi u celom programu.
EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str)
{
    // `SQLCODE` je makro koji se razvija u `sqlca.sqlcode`
    if(sqlca.sqlcode < 0)
    {
        char Buffer[1024];
        short BufferSize = sizeof(Buffer);
        short LineWidth = 50;
        sqlaintp(Buffer, BufferSize, LineWidth, &sqlca);

        printf("Greska %d: %s\n", sqlca.sqlcode, str);
        printf("%s\n", Buffer);
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

4.2 Naredba EXECUTE IMMEDIATE

Naredba EXECUTE IMMEDIATE priprema izvršivi oblik dinamičke SQL naredbe od njegovog tekstualnog oblika i odmah zatim ga i izvršava. Može biti korišćena za pripremu i izvršavanje SQL naredbi koje ne sadrže ni matične promenljive ni parametarske oznake.

Sintaksa ove naredbe je data u nastavku:

EXECUTE IMMEDIATE <IZRAZ>

Vrednost <IZRAZ> predstavlja izraz koji sadrži naredbu koja će se izvršiti. Izraz mora izračunavati nisku čija je najveća veličina 2 MB. Deo spiska naredbi koje mogu biti dinamički izvršene korišćenjem ove SQL naredbe je dat u nastavku:

Niska koja sadrži naredbu ne sme imati parametarske oznake ili reference na matične promenljive i ne sme počinjati ključnim rečima EXEC SQL. Takođe, onda ne sme sadržati operator završavanja naredbe sa izuzetkom složenih SQL naredbi koje mogu sadržati tačku-zapetu (;) za razdvajanje naredbi u bloku izvršavanja.

Kada se naredba EXECUTE IMMEDIATE izvrši, specifikovana naredba se parsira i proveravaju se greške. Ako SQL naredba nije validna, ona se neće izvršiti i uslov koji je doveo do greške se izveštava kroz SQLCA. Ako je SQL naredba validna, ali njeno izvršavanje dovodi do greške, onda se ta greška takođe izveštava kroz SQLCA.

Zadatak 4.1: Napisati:

  • C/SQL program u kojem se naredbe izvršavaju dinamički koji čita SQL naredbu iz datoteke čiji se naziv zadaje kao prvi argument komandne linije. SQL naredba se čita do pojave karaktera ; ili do kraja datoteke, ispisuje se korisniku, a potom se izvršava. Pretpostaviti da korisnik neće uneti naredbu SELECT, kao ni da neće sadržati parametarske oznake. Pretpostaviti da naredba koja se čita iz datoteke nije duža od 255 karaktera.

  • Datoteku koja sadrži SQL naredbu koja povećava ESPB bodove svim predmetima za 1. Izvršiti naredbu iz ove datoteke napisanim programom.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_4/zadatak_4_1.sqc:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sql.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
// Maticna promenljiva koja ce cuvati tekstualni oblik dinamicke SQL naredbe
char hStatementString[255];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(sqlca.sqlcode < 0) {
        char buffer[1024];
        short bufferSize = sizeof(buffer);
        short lineWidth = 50;
        sqlaintp(buffer, bufferSize, lineWidth, &sqlca);

        fprintf(stderr, "Greska %d: %s\n", sqlca.sqlcode, str);
        fprintf(stderr, "%s\n", buffer);
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[]) {
    // Proveravamo argumente i datoteku za citanje
    // pre nego sto se uopste povezemo
    if (argc != 2) {
        printf("Niste naveli datoteku za citanje!\n");
        exit(EXIT_FAILURE);
    }

    FILE *ulaz = fopen(argv[1], "r");
    if (ulaz == NULL) {
        printf("Postoji problem sa citanjem datoteke \"%s\"!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    int c; // `int` umesto `char` zbog EOF
    int broj_karaktera = 0;
    while(broj_karaktera < sizeof(hStatementString)) {
        c = fgetc(ulaz);
        
        if (c == ';' || c == EOF) {
            break;
        }
        
        hStatementString[broj_karaktera++] = (char)c;
    }

    fclose(ulaz);

    // Ispisujemo korisniku sta je uneo
    printf("\nIzvrsavam naredbu:\n");
    printf("***************************\n");
    printf("%s\n", hStatementString);
    printf("***************************\n");
    
    // Pripremamo i izvrsavamo unetu naredbu
    EXEC SQL EXECUTE IMMEDIATE :hStatementString;
    checkSQL("Execute immediate");
    
    printf("Naredba je uspesno izvrsena.\n");

    EXEC SQL CONNECT RESET;
    checkSQL("Connect reset");

    return 0;
}

Datoteka: vezbe/primeri/poglavlje_4/zadatak_4_1.sql:

-- Otkomentarisati narednu liniju i zakomentarisati liniju ispod nje kako bi se prikazala detaljna greska u programu.
-- UPDATE   PREDMET
UPDATE  DA.PREDMET
SET     ESPB = ESPB + 1;

Pokretanje: ./zadatak_4_1 zadatak_4_1.sql

4.3 Naredba PREPARE

Naredba PREPARE se koristi od strane aplikativnog programa da dinamički pripremi SQL naredbu za izvršenje. Ova naredba kreira izvršivu SQL naredbu koja se naziva pripremljena naredba (engl. prepared statement) na osnovu tekstualnog oblika naredbe koji se naziva niska naredbe (engl. statement string).

Sintaksa ove naredbe data je u nastavku:

PREPARE <NAZIV_NAREDBE>
[[OUTPUT] INTO <NAZIV_OPISIVAČA_IZLAZA>]
[INPUT INTO <NAZIV_OPISIVAČA_ULAZA>]
FROM (<MATIČNA_PROMENLJIVA>|<IZRAZ>)

Specifikovanjem vrednosti za <NAZIV_NAREDBE> se imenuje pripremljena naredba. Ako se isti naziv koristi za identifikaciju već postojeće pripremljene naredbe, onda se ta, prethodno pripremljena naredba, uništava. Ipak, postoji izuzetak od ovog pravila: <NAZIV_NAREDBE> ne sme već idenfitikovati pripremljenu naredbu koja predstavlja SELECT naredbu u otvorenom kursoru.

Pripremljena naredba se izračunava na osnovu niske naredbe koja se nalazi u vrednosti u FROM klauzi. Može se navesti bilo matična promenljiva čiji je identifikator <MATIČNA_PROMENLJIVA> i tip niska ili izraz <IZRAZ> koji vraća nisku.

U zavisnosti od korišćenja narednih klauza, ova naredba ima sledeće efekte:

Umesto ovih klauza se može koristiti naredba DESCRIBE o kojoj će biti reči u kasnijem tekstu.

Naredbe koje se mogu pripremiti na ovaj način su iste one naredbe kao za naredbu EXECUTE IMMEDIATE, uz dodatak SELECT naredbe.

4.3.1 Parametarske oznake

Iako dinamička SQL naredba koja se priprema ne može da sadrži reference na matične promenljive, ona ipak može da koristi parametarske oznake (engl. parameter marker). Parametarske oznake mogu biti zamenjene vrednostima iz matičnih promenljivih u trenutku izvršavanja pripremljene naredbe.

Postoje dva načina za specifikovanje parametarskih oznaka:

Oba opisana načina se koriste na istom mestu gde bi se koristile matične promenljive u slučaju da je SQL naredba statička. Iako imenovane parametarske oznake imaju istu sintaksu kao i matične promenljive, njihova upotrebna vrednost je svakako drugačija. Matična promenljiva sadrži vrednost u memoriji i koristi se direktno u statičkoj SQL naredbi. Imenovana parametarska oznaka predstavlja tek mesto za zamenu (engl. placeholder) za vrednost u dinamičkoj SQL naredbi i njena vrednost se navodi prilikom izvršavanja pripremljene naredbe.

Takođe, parametarske oznake imaju i dva tipa:

UPDATE  DA.ISPIT
SET     OCENA = 10
WHERE   INDEKS = ?

koristi se netipizirana parametarska oznaka kao predikat restrikcije, čiji će tip biti implicitno postavljen na onaj tip kojim je definisana kolona INDEKS u tabeli DA.ISPIT. Netipizirane parametarske oznake se mogu koristiti u dinamičkim SQL naredbama sve dok se tip parametarske oznake može izvesti iz konteksta upotrebe. U suprotnom, SUBP prijavljuje SQLSTATE vrednost 42610 (SQLCODE -418).

CAST(? AS <TIP_PODATAKA>)

gde je <TIP_PODATAKA> odgovarajući Db2 tip. Ova notacija ne predstavlja poziv funkcije ili eksplicitnu konverziju, već tek “obećanje” da će tip vrednosti, koja će se koristiti umesto navedene tipizirane parametarske oznake kada se naredba bude izvršavala, odgovarati tipu koji je naveden ili da se makar može konvertovati u njega. Na primer, u SQL naredbi

UPDATE  DA.DOSIJE
SET     PREZIME = TRANSLATE(CAST(? AS VARCHAR(50)))
WHERE   INDEKS = 20150050

vrednost argumenta funkcije TRANSLATE biće navedena tokom faze izvršavanja. Očekuje se da je tip te vrednosti bilo VARCHAR(50) ili tip koji se može konvertovati u njega. Tipizirane parametarske oznake se mogu koristiti u dinamičkim SQL naredbama gde god se očekuje matična promenljiva i tip podataka je kompatibilan sa “obećanjem” navedenim u CAST funkciji.

Kada se naredba PREPARE izvrši, specifikovana naredba se parsira i proveravaju se greške. Ako SQL naredba nije validna, ona se neće izvršiti i uslov koji je doveo do greške se izveštava kroz SQLCA strukturu. Svako naredno izvršavanje naredbi EXECUTE ili OPEN koji referišu na ovu naredbu će takođe dobiti istu grešku, osim ukoliko se prethodno ne ispravi.

Pripremljena naredba može biti referisana u narednim naredbama, sa odgovarajućim ograničenjima navedenim u zagradama:

Prednost korišćenja pripremljenih naredbi je u činjenici da se one mogu izvršavati više puta, sa potencijalno različitim vrednostima parametarskih oznaka. Dakle, jednom pripremljenu naredbu nema potrebe pripremati opet. Ipak, ukoliko naredba koja nije SELECT ne sadrži parametarske oznake i izvršava se samo jednom, onda se preporučuje korišćenje EXECUTE IMMEDIATE naredbe umesto korišćenja kombinacije naredbi PREPARE i EXECUTE.

4.4 Naredba EXECUTE

Naredba EXECUTE izvršava prethodno pripremljenu SQL naredbu koja nije SELECT. Sintaksa ove naredbe je data u nastavku:

EXECUTE <NAZIV_NAREDBE>
[INTO DESCRIPTOR <NAZIV_OPISIVAČA_IZLAZA>]
[USING (<LISTA_MATIČNIH_PROMENLJIVIH> | DESCRIPTOR <NAZIV_OPISIVAČA_ULAZA>)]

Ova naredba izvršava prethodno pripremljenu naredbu koja se identifikuje pomoću <NAZIV_NAREDBE>. Ta vrednost mora odgovarati nekoj prethodno pripremljenoj naredbi. Pripremljena naredba se može izvršiti više puta. Ukoliko pripremljena naredba sadrži parametarske oznake, onda možemo menjati vrednosti parametarskih oznaka svaki put kada izvršavamo naredbu, ukoliko za time ima potrebe.

U zavisnosti od korišćenja narednih klauza, ova naredba ima sledeće efekte:

Pre samog izvršavanja pripremljene naredbe, svaka ulazna parametarska oznaka se zamenjuje vrednošću odgovarajuće promenljive ili izraza. Za tipizirane parametarske oznake, atributi ciljne promenljive ili izraza su oni koji su navedeni u CAST specifikaciji. Za netipizirane parametarske oznake, atributi ciljne promenljive ili izraza se izvode iz konteksta upotrebe parametarske oznake.

Zadatak 4.2: Napisati C/SQL program u kojem se naredbe izvršavaju dinamički kojim se omogućava da korisnik unese podatke o novom ispitnom roku. Uneti novi ispitni rok u bazu podataka sa unetim podacima. Obratiti pažnju da se naziv roka može sadržati od više reči.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_4/zadatak_4_2.sqc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sql.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
char hStatementString[255];
short hSchoolYear;
char hFinalLabel[21], 
     hFinalName[31], 
     hFinalStartDate[11], 
     hFinalEndDate[11];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(sqlca.sqlcode < 0) {
        char buffer[1024];
        short bufferSize = sizeof(buffer);
        short lineWidth = 50;
        sqlaintp(buffer, bufferSize, lineWidth, &sqlca);

        fprintf(stderr, "Greska %d: %s\n", sqlca.sqlcode, str);
        fprintf(stderr, "%s\n", buffer);
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");

    // Kreiramo tekstualni oblik dinamicke SQL naredbe sa netipiziranim i neimenovanim parametarskim oznakama.
    sprintf(hStatementString, "INSERT INTO DA.ISPITNIROK VALUES (?, ?, ?, ?, ?)");

    // Kreiramo pripremljenu naredbu od njenog tekstualnog oblika
    EXEC SQL 
        PREPARE pStatement 
        FROM    :hStatementString;
    checkSQL("PREPARE");
    
    // Ucitavamo podatke za unosenje u tabelu ISPITNIROK
    printf("Unesite godinu roka:\n");
    scanf("%hd", &hSchoolYear);

    printf("Unesite oznaku roka:\n");
    scanf("%s", hFinalLabel);
    getchar(); // Citanje novog reda zbog poziva fgets() u nastavku
    
    printf("Unesite naziv roka:\n");
    // Funkcija fgets ce procitati i karakter za novi red na kraju unosa,
    // tako da moramo da ga postavimo na terminirajucu nulu.
    fgets(hFinalName, sizeof(hFinalName), stdin);
    hFinalName[strcspn(hFinalName, "\n")] = '\0';
    
    printf("Unesite datum pocetka prijave roka:\n");
    scanf("%s", hFinalStartDate);

    printf("Unesite datum kraja prijave roka:\n");
    scanf("%s", hFinalEndDate);

    // Izvrsavamo prethodno pripremljenu naredbu koriscenjem maticnih promenljivih
    EXEC SQL 
        EXECUTE pStatement 
        USING   :hSchoolYear, 
                :hFinalLabel, 
                :hFinalName, 
                :hFinalStartDate, 
                :hFinalEndDate;
    checkSQL("Execute");
    
    printf("Novi ispitni rok je uspesno unet!\n");

    EXEC SQL CONNECT RESET;
    checkSQL("Connect reset");

    return 0;
}

4.5 Naredba DECLARE CURSOR sa dinamičkim SQL naredbama

Kao što smo rekli, moguće je pripremiti dinamičku SQL naredbu SELECT naredbom PREPARE. Međutim, ovakvu naredbu nije moguće izvršiti naredbom EXECUTE. Umesto toga, koristimo isti pristup rada sa kursorima, sa razlikom da se prilikom deklaracije kursora koristi pripremljena naredba umesto kursora, kao i da se prilikom otvaranja kursora navode matične promenljive ili izrazi koji će biti zamenjeni umesto parametarskih oznaka u SELECT naredbi, ako ih takva naredba sadrži. Naredni zadatak ilustruje rad sa dinamičkom SQL naredbom SELECT.

Zadatak 4.3: Napisati C/SQL program u kojem se naredbe izvršavaju dinamički. Izdvojiti naredne podatke o studijskim programima: identifikator, oznaka i naziv, za svaki studijski program čiji se identifikator nivoa unosi sa standardnog ulaza.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_4/zadatak_4_3.sqc:

#include <stdio.h>
#include <stdlib.h>
#include <sql.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
char hStatementString[255];

sqlint32 hStudyProgramId;
char hStudyProgramLabel[11];
char hStudyProgramName[51];
short hDegreeLevelId;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(sqlca.sqlcode < 0) {
        char buffer[1024];
        short bufferSize = sizeof(buffer);
        short lineWidth = 50;
        sqlaintp(buffer, bufferSize, lineWidth, &sqlca);

        fprintf(stderr, "Greska %d: %s\n", sqlca.sqlcode, str);
        fprintf(stderr, "%s\n", buffer);
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    // Kreiranje tekstualne reprezentacije naredbe sa tipiziranom parametarskom oznakom.
    // Primetite da se na kraju svakog reda nalazi karakter razmaka.
    // Ovo je neophodno zato sto ce C kompilator samo nadovezati ove niske,
    // pa ako ne navedemo razmake, onda ce "slepiti" imena kolona i klauza.
    sprintf(hStatementString, 
        "SELECT ID, "
        "       OZNAKA, "
        "       RTRIM(NAZIV) "
        "FROM   DA.STUDIJSKIPROGRAM "
        "WHERE  IDNIVOA = CAST(? AS SMALLINT)");
    
    // Prevodjenje tekstualne reprezentacije naredbe u pripremljenu naredbu
    EXEC SQL 
        PREPARE pStatement 
        FROM    :hStatementString;
    checkSQL("Prepare");

    // S obzirom da koristimo SELECT naredbu, ne mozemo da koristimo EXECUTE.
    // Umesto toga, moramo da deklarisemo kursor na osnovu pripremljene naredbe.
    // Ne navodimo dvotacku ispred pStatement jer je to identifikator, a ne maticna promenljiva.
    EXEC SQL 
        DECLARE cStudyPrograms CURSOR FOR 
        pStatement;
    checkSQL("Declare cursor");

    printf("Unesite identifikator nivoa studija: ");
    scanf("%hd", &hDegreeLevelId);

    // Otvaranje kursora i navodjenje vrednosti parametarske oznake
    EXEC SQL 
        OPEN    cStudyPrograms 
        USING   :hDegreeLevelId;
    checkSQL("Open cursor");
    
    printf("+-------------------------------------------+\n");
    printf("|             STUDIJSKI PROGRAM             |\n");
    printf("+-----+------+------------------------------+\n");
    printf("|ID   |OZNAKA|NAZIV                         |\n");
    printf("|-----+------+------------------------------+\n");

    for(;;) {
        EXEC SQL 
            FETCH   cStudyPrograms
            INTO    :hStudyProgramId, 
                    :hStudyProgramLabel, 
                    :hStudyProgramName;
        checkSQL("Fetch cursor");

        if (SQLCODE == 100) {
            break;
        }

        printf("|%-5d|%-6s|%30.30s|\n", hStudyProgramId, hStudyProgramLabel, hStudyProgramName);
        printf("+-----+------+------------------------------+\n");
    }
    
    printf("+-----+------+------------------------------+\n");

    EXEC SQL CLOSE cStudyPrograms;
    checkSQL("Close cursor");

    EXEC SQL CONNECT RESET;
    checkSQL("Connect reset");
    
    return 0;
}

4.6 SQL prostor za opisivanje (SQLDA)

SQL prostor za opisivanje (engl. SQL Description Area, skr. SQLDA) predstavlja kolekciju promenljivih koje su neophodne za dohvatanje informacija o dinamičkim SQL naredbama. Da bismo mogli da ga koristimo, zaglavlje je potrebno uključiti pomoću naredbe INCLUDE:

EXEC SQL INCLUDE SQLDA;

Promenljive deklarisane u SQLDA zaglavlju predstavljaju podešavanja koja se mogu koristiti u naredbama poput PREPARE, OPEN, FETCH i EXECUTE. SQLDA komunicira sa dinamičkom SQL naredbom i ima nekoliko upotrebnih vrednosti. Značenje informacija u SQLDA zavisi od njenog korišćenja. U naredbama PREPARE i DESCRIBE, koristi se kao izvor informacija ka aplikaciji o pripremljenoj naredbi. U naredbama OPEN, EXECUTE i FETCH, SQLDA nosi informacije o matičnim promenljivama.

U nastavku sledi opis strukture SQLDA, a grafički prikaz se nalazi na narednoj slici.

"Struktura SQLDA."

Zaglavlje strukture se sastoji od narednih informacija:

Zaglavlje je praćeno proizvoljnim brojem pojavljivanja struktura koje se nazivaju SQLVAR. U naredbama OPEN, FETCH i EXECUTE svako pojavljivanje SQLVAR opisuje matičnu promenljivu. U naredbama DESCRIBE i PREPARE svako pojavljivanje SQLVAR opisuje kolonu rezultujuće tabele ili parametarsku oznaku. Svaka SQLVAR struktura ima naredna polja:

Naredna tabela opisuje SQL tipove kolona i njihove odgovarajuće SQLTYPE tipove u SQLDA. Primetimo da svaki simbolički naziv počinje imenom SQL_TYP_ ukoliko vrednost nije potencijalno nedostajuća, a imenom SQL_TYP_N ukoliko jeste.

SQL tip kolone SQLTYPE numerička vrednost SQLTYPE simboličko ime
DATE 384/385 SQL_TYP_DATE / SQL_TYP_NDATE
TIME 388/389 SQL_TYP_TIME / SQL_TYP_NTIME
TIMESTAMP 392/393 SQL_TYP_STAMP / SQL_TYP_NSTAMP
n/a 400/401 SQL_TYP_CGSTR / SQL_TYP_NCGSTR
BLOB 404/405 SQL_TYP_BLOB / SQL_TYP_NBLOB
CLOB 408/409 SQL_TYP_CLOB / SQL_TYP_NCLOB
DBCLOB 412/413 SQL_TYP_DBCLOB / SQL_TYP_NDBCLOB
VARCHAR 448/449 SQL_TYP_VARCHAR / SQL_TYP_NVARCHAR
CHAR 452/453 SQL_TYP_CHAR / SQL_TYP_NCHAR
LONG VARCHAR 456/457 SQL_TYP_LONG / SQL_TYP_NLONG
n/a 460/461 SQL_TYP_CSTR / SQL_TYP_NCSTR
VARGRAPHIC 464/465 SQL_TYP_VARGRAPH / SQL_TYP_NVARGRAPH
GRAPHIC 468/469 SQL_TYP_GRAPHIC / SQL_TYP_NGRAPHIC
LONG VARGRAPHIC 472/473 SQL_TYP_LONGRAPH / SQL_TYP_NLONGRAPH
FLOAT 480/481 SQL_TYP_FLOAT / SQL_TYP_NFLOAT
REAL 480/481 SQL_TYP_FLOAT / SQL_TYP_NFLOAT
DECIMAL 484/485 SQL_TYP_DECIMAL / SQL_TYP_DECIMAL
INTEGER 496/497 SQL_TYP_INTEGER / SQL_TYP_NINTEGER
SMALLINT 500/501 SQL_TYP_SMALL / SQL_TYP_NSMALL
n/a 804/805 SQL_TYP_BLOB_FILE / SQL_TYPE_NBLOB_FILE
n/a 808/809 SQL_TYP_CLOB_FILE / SQL_TYPE_NCLOB_FILE
n/a 812/813 SQL_TYP_DBCLOB_FILE / SQL_TYPE_NDBCLOB_FILE
n/a 960/961 SQL_TYP_BLOB_LOCATOR / SQL_TYP_NBLOB_LOCATOR
n/a 964/965 SQL_TYP_CLOB_LOCATOR / SQL_TYP_NCLOB_LOCATOR
n/a 968/969 SQL_TYP_DBCLOB_LOCATOR / SQL_TYP_NDBCLOB_LOCATOR
XML 988/989 SQL_TYP_XML / SQL_TYP_XML

4.6.1 Efekat naredbe EXECUTE na SQLDA strukturu

Pre nego što se naredba EXECUTE izvrši, korisnik mora da postavi naredna polja u ulaznoj SQLDA strukturi:

Pritom, SQLDA mora imati dovoljno alociranog prostora da sadrži sva pojavljivanja SQLVAR.

Procedura za alociranje dovoljne veličine prostora glasi:

Drugim rečima, količina prostora koju treba alocirati je (SQLD je odgovarajući broj matičnih promenljivih opisanih pojavljivanjima SQLVAR):

16 + (SQLD * sizeof(struct sqlvar))

Na raspolaganju nam je makro SQLDASIZE koji će izračunati ovu vrednost za nas, uzimajući u obzir specifičnosti platforme. Njegov obavezni argument je upravo vrednost SQLD. Dodatno, SQLD mora biti postavljen na vrednost veću ili jednaku nuli i manju od ili jednaku SQLN.

4.6.2 Efekat naredbe PREPARE na SQLDA strukturu

Prilikom izvršavanja naredbe PREPARE INPUT, SUBP uvek postavlja SQLD na broj ulaznih parametarskih oznaka u naredbi.

Prilikom izvršavanja naredbe PREPARE OUTPUT, SUBP uvek postavlja SQLD na broj kolona u rezultujućoj tabeli ili na broj izlaznih parametarskih oznaka.

4.7 Naredba DESCRIBE

Naredba DESCRIBE dohvata informacije o pripremljenoj naredbi. Postoje dva tipa informacija koja se mogu dobiti ovom naredbom, i svaki od njih ćemo posebno opisati.

4.7.1 Naredba DESCRIBE INPUT

Ova naredba dohvata informacije o ulaznim parametarskim oznakama u pripremljenoj naredbi. Ova informacija se smešta u SQLDA strukturu.

Sintaksa ove naredbe je:

DESCRIBE INPUT <NAZIV_NAREDBE> INTO <NAZIV_OPISIVAČA>

Ova naredba dohvata informacije iz prethodno pripremljene naredbe koja se identifikuje imenom <NAZIV_NAREDBE>. Klauza INTO <NAZIV_OPISIVAČA> identifikuje SQLDA strukturu za unos informacija.

Prilikom izvršavanja naredbe DESCRIBE INPUT, SUBP uvek postavlja SQLD na broj ulaznih parametarskih oznaka u naredbi.

Priprema SQLDA strukture za izvršavanje naredbe

Pre izvršavanja DESCRIBE INPUT naredbe, korisnik mora da alocira prostor za SQLDA strukturu i da postavi vrednost promenljive SQLN na broj SQLVAR pojavljivanja u okviru SQLDA strukture. Ova vrednost mora biti veća od nule ili jednaka nuli, pre nego što se naredba izvrši.

Postoje tri tehnike za alociranje SQLDA strukture:

  1. Alocirati prostor za SQLDA strukturu sa dovoljnim brojem SQLVAR promenljivih da sadrži bilo koju listu informacija koju aplikacija može da procesira. Najveći dozvoljen broj informacija iznosi 255. Ova tehnika koristi veliku količinu prostora koja u praktičnim primenama neće biti iskorišćena.

  2. Ponavljati naredna dva koraka:

  1. Alocirati prostor za SQLDA strukturu koja je dovoljno velika da sadrži veliki broj SQLVAR pojavljivanja, ako ne i sve liste informacija koju aplikacija može da procesira, ali je takođe i dovoljno mala da ne bude previše viška memorije utrošeno. Ukoliko je ovo dovoljna količina prostora, nastaviti sa izvršavanjem. U suprotnom, alocirati novi prostor sa dovoljnim brojem SQLVAR pojavljivanja i izvršiti naredbu DESCRIBE ponovo. Ova tehnika predstavlja kompromis između prethodne dve tehnike i oslanja se na heuristiku izbora veličine prostora koja je dovoljno velika, a u isto vreme i dovoljno mala.

Efekat izvršenja naredbe

Nakon izvršavanja naredbe DESCRIBE INPUT, SUBP dodeljuje vrednosti promenljivama strukture SQLDA na sledeći način:

4.7.2 Naredba DESCRIBE OUTPUT

Ova naredba dohvata informacije o pripremljenoj naredbi ili informacije o listi kolona u pripremljenoj SELECT naredbi. Ova informacija se smešta u SQLDA strukturu.

Sintaksa ove naredbe je:

DESCRIBE [OUTPUT] <NAZIV_NAREDBE> INTO <NAZIV_OPISIVAČA>

Ova naredba dohvata informacije iz perthodno pripremljene naredbe koja se identifikuje imenom <NAZIV_NAREDBE>. Ako je pripremljena naredba SELECT ili VALUES INTO, onda informacija koja se dohvata predstavlja informaciju o kolonama rezultujuće tabele. Klauza INTO <NAZIV_OPISIVAČA> identifikuje SQLDA strukturu za unos informacija.

Prilikom izvršavanja naredbe DESCRIBE OUTPUT, SUBP uvek postavlja SQLD na broj kolona u rezultujućoj tabeli ili na broj izlaznih parametarskih oznaka.

Pre izvršavanja DESCRIBE OUTPUT naredbe, korisnik mora da alocira prostor za SQLDA strukturu i da postavi vrednost promenljive SQLN na broj SQLVAR pojavljivanja u okviru SQLDA strukture. Ova vrednost mora biti veća od nule ili jednaka nuli, pre nego što se naredba izvrši.

Postoje tri tehnike za alociranje SQLDA strukture:

  1. Alocirati prostor za SQLDA strukturu sa dovoljnim brojem SQLVAR promenljivih da sadrži bilo koju listu informacija koju aplikacija može da procesira. Najveći dozvoljen broj informacija iznosi 255. Ova tehnika koristi veliku količinu prostora koja u praktičnim primenama neće biti iskorišćena.

  2. Ponavljati naredna dva koraka:

  1. Alocirati prostor za SQLDA strukturu koja je dovoljno velika da sadrži veliki broj SQLVAR pojavljivanja, ako ne i sve liste informacija koju aplikacija može da procesira, ali je takođe i dovoljno mala da ne bude previše viška memorije utrošeno. Ukoliko je ovo dovoljna količina prostora, nastaviti sa izvršavanjem. U suprotnom, alocirati novi prostor sa dovoljnim brojem SQLVAR pojavljivanja i izvršiti naredbu DESCRIBE ponovo. Ova tehnika predstavlja kompromis između prethodne dve tehnike i oslanja se na heuristiku izbora veličine prostora koja je dovoljno velika, a u isto vreme i dovoljno mala.

Efekat izvršenja naredbe

Nakon izvršavanja naredbe DESCRIBE OUTPUT, SUBP dodeljuje vrednosti promenljivama strukture SQLDA na sledeći način:

Naredni zadatak ilustruje sve opisane tehnike za dizajn programa koji može da izvrši proizvoljnu dinamičku SQL naredbu.

Zadatak 4.4: Napisati C/SQL program u kojem se naredbe izvršavaju dinamički. Omogućiti izvršavanje proizvoljne naredbe. Pretpostaviti da su jedini tipovi podataka koji se koriste numerički, tekstualni i datumi. Pretpostaviti da naredba koja se unosi sa standardnog ulaza nije duža od 512 karaktera.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_4/zadatak_4_4.sqc:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sql.h>

// Za SQLTYPE simbolicka imena
#include <sqlenv.h>

// Za SQLDA strukture 
#include <sqlda.h>

// Deklaracija SQL prostora za komunikaciju
EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
char hStatementString[512];
EXEC SQL END DECLARE SECTION;

// Deklarisemo SQLDA promenljive za opisivanje dinamickih naredbi
struct sqlda *hInputSqldaPtr = NULL;
struct sqlda *hOutputSqldaPtr = NULL;

// Enumerator za tip DESCRIBE naredbe
enum DescribeType {
    INPUT,
    OUTPUT
};

//////////////////////////////////////////////
// BEGIN funkcije za obradu dinamickih naredbi

// Ucitavanje i pripremanje dinamicke naredbe 
void readAndPrepareStatement();

// Dohvatanje informacija o ulaznim i izlaznim parametrima pripremljene naredbe
void prepareSqlda(struct sqlda **sqldaptr, enum DescribeType d_type);

// Upravljanje memorijom za podatke
void prepareDataForExecutingStatement();
void alocateSpaceForSqlda(struct sqlda *sqldaptr);
void markFirstISqlvarsForDeletion(struct sqlda *sqldaptr, int i);
void freeSqlda();

// Ucitavanje ulaznih podataka
void askForParameterMarkerValues();

// Izvrsavanje naredbi
void executeStatement();
void executeNotSelectStatement();
void executeSelectStatement();
double getDoubleFromDecimal(char *sqldata, short sqllen);

// END funkcije za obradu dinamickih naredbi
////////////////////////////////////////////

// Funkcije za obradu gresaka
void checkSQL(const char *str, int linenum);
void exitGracefully();

// Makro koji poziva funkciju `checkSQL` za obradu greske,
// pri cemu ce proslediti liniju u fajlu gde je doslo do greske
#define IS_ERROR_WITH_LINE(err) checkSQL(err, __LINE__)

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    IS_ERROR_WITH_LINE("Connect");
    
    readAndPrepareStatement();    
    prepareSqlda(&hInputSqldaPtr, INPUT);
    prepareSqlda(&hOutputSqldaPtr, OUTPUT);
    prepareDataForExecutingStatement();
    executeStatement();
    
    printf("\n##########################################################\n");
    printf("%s", hStatementString);
    printf("\n##########################################################\n");
    
    freeSqlda();

    EXEC SQL COMMIT;
    IS_ERROR_WITH_LINE("Potvrdjivanje izmena");

    EXEC SQL CONNECT RESET;
    IS_ERROR_WITH_LINE("Connect reset");

    return 0;
}

void readAndPrepareStatement() {
    printf("Unesite SQL naredbu do %d karaktera:\n", (int)(sizeof(hStatementString)));

    char c;
    int broj_karaktera = 0;
    while (broj_karaktera < sizeof(hStatementString)) {
        c = getchar();
        
        if (c == ';') {
            break;
        }
        
        hStatementString[broj_karaktera] = c;
        ++broj_karaktera;
    }
    
    EXEC SQL PREPARE pStatement FROM :hStatementString;
    IS_ERROR_WITH_LINE("Prepare");
}

void prepareSqlda(struct sqlda **sqldaptr, enum DescribeType d_type) {
    // Ova funkcija koristi tehniku 2, koja sadrzi dva koraka,
    // za alokaciju najmanje potrebne memorije za SQLDA strukturu.
    
    // Korak 1:
    // Alocirati prostor samo za zaglavlje strukture
    *sqldaptr = (struct sqlda*)malloc(SQLDASIZE(0));
    if(*sqldaptr == NULL) {
        fprintf(stderr, "Neuspesna alokacija SQLDA zaglavlja\n\n");
        exitGracefully();
    }

    // Postaviti velicinu SQLN na 0
    (*sqldaptr)->sqln = 0;

    // Izvrsiti prvi put naredbu DESCRIBE 
    if (d_type == INPUT) {
        EXEC SQL DESCRIBE INPUT pStatement 
            INTO :**sqldaptr;
    } else if (d_type == OUTPUT) {
        EXEC SQL DESCRIBE OUTPUT pStatement 
            INTO :**sqldaptr;
    } else {
        fprintf(stderr, "Nepoznat tip DESCRIBE naredbe\n\n");
        exitGracefully();
    }
    IS_ERROR_WITH_LINE("Describe - Korak 1");

    // Korak 2:
    int numcols;
    
    // Ukoliko nismo alocirali dovoljno prostora za sve SQLVAR
    if(SQLCODE == 236) {
        // Dohvatamo broj kolona u naredbi
        numcols = (*sqldaptr)->sqld;
        // Oslobadjamo prethodno alociranu strukturu
        free(*sqldaptr);
        *sqldaptr = NULL;
        // Alociramo novu strukturu 
        // sa ispravnim brojem SQLVAR promenljivih
        *sqldaptr = (struct sqlda*)malloc(SQLDASIZE(numcols));
        
        if(*sqldaptr == NULL) {
            fprintf(stderr, "Neuspesna alokacija SQLDA strukture\n\n");
            exitGracefully();    
        }    
    }

    // Postavljamo ispravnu vrednost za SQLN
    (*sqldaptr)->sqln = numcols;

    // Izvrsavamo finalnu naredbu DESCRIBE OUTPUT
    if (d_type == INPUT) {
        EXEC SQL DESCRIBE INPUT pStatement 
            INTO :**sqldaptr;
    } else if (d_type == OUTPUT) {
        EXEC SQL DESCRIBE OUTPUT pStatement 
            INTO :**sqldaptr;
    } else {
        fprintf(stderr, "Nepoznat tip DESCRIBE naredbe\n\n");
        exitGracefully();
    }

    IS_ERROR_WITH_LINE("Describe - Korak 2");
}

void prepareDataForExecutingStatement() {
    alocateSpaceForSqlda(hInputSqldaPtr);
    alocateSpaceForSqlda(hOutputSqldaPtr);
    askForParameterMarkerValues();
}

void alocateSpaceForSqlda(struct sqlda *sqldaptr) {
    int i;
    short sqltype, sqllen;
    for(i = 0; i < sqldaptr->sqld; ++i) {
        sqltype = sqldaptr->sqlvar[i].sqltype;
        sqllen = sqldaptr->sqlvar[i].sqllen;
        
        // Za svaku kolonu alociramo maticnu promenljivu kao i indikatorsku promenljivu.
        sqldaptr->sqlvar[i].sqldata = (char*)calloc(1, sqllen);
        sqldaptr->sqlvar[i].sqlind = (short*)calloc(1, sizeof(short));
        
        // Proveravamo da li smo dohvatili tip koji je podrzan ovom implementacijom
        switch (sqltype) {
            // Brojevni tipovi su podrzani.
            case SQL_TYP_SMALL:
            case SQL_TYP_NSMALL:
            case SQL_TYP_INTEGER:
            case SQL_TYP_NINTEGER:
            case SQL_TYP_FLOAT:
            case SQL_TYP_NFLOAT:
            case SQL_TYP_DECIMAL:
            case SQL_TYP_NDECIMAL:
                break;
            
            // Posebno, sve "tekstualne" kolone cemo procesirati na isti nacin:
            // 1) Ako ne mogu imati NULL vrednost, koristicemo tip SQL_TYP_CSTR.
            case SQL_TYP_DATE:
            case SQL_TYP_TIME:
            case SQL_TYP_STAMP:
            case SQL_TYP_VARCHAR:
            case SQL_TYP_CHAR:
            case SQL_TYP_CSTR:
            case SQL_TYP_LONG:
                sqldaptr->sqlvar[i].sqltype = SQL_TYP_CSTR;
                break;
                
            // 2) Ako mogu imati NULL vrednost, koristicemo tip SQL_TYP_NCSTR.
            case SQL_TYP_NDATE:
            case SQL_TYP_NTIME:
            case SQL_TYP_NSTAMP:
            case SQL_TYP_NVARCHAR:
            case SQL_TYP_NCHAR:
            case SQL_TYP_NCSTR:
            case SQL_TYP_NLONG:
                sqldaptr->sqlvar[i].sqltype = SQL_TYP_NCSTR;
                break;
                
            // XML, LOB, GRAPHIC i slicne tipove ne podrzavamo.
            default:
                printf("Nepoznat tip: %hd\n", sqltype);
                break;
        }
        
        // Provera alokacije za promenljivu
        if (sqldaptr->sqlvar[i].sqldata == NULL) {
            printf("Neuspesna alokacija SQLVAR za kolonu %d\n", i);
            
            // Oslobadjamo sve alocirane prostore do sada
            markFirstISqlvarsForDeletion(sqldaptr, i);
            exitGracefully();
        }
        // Provera alokacije za indikatorsku promenljivu
        if (sqldaptr->sqlvar[i].sqlind == NULL) {
            printf("Neuspesna alokacija SQLVAR (indikator) za kolonu %d\n", i);
            
            // Prvo oslobadjamo promenljivu
            free(sqldaptr->sqlvar[i].sqldata);
            
            // Pa onda i sve prethodne 
            markFirstISqlvarsForDeletion(sqldaptr, i);
            exitGracefully();
        }
    }
}

void markFirstISqlvarsForDeletion(struct sqlda *sqldaptr, int i) {
    sqldaptr->sqld = i;
}

void freeSqlda() {
    if (hInputSqldaPtr != NULL) {
        int i = 0;
        for(; i < hInputSqldaPtr->sqld; ++i) {
            free(hInputSqldaPtr->sqlvar[i].sqldata);
            free(hInputSqldaPtr->sqlvar[i].sqlind);
        }

        free(hInputSqldaPtr);
    }

    if (hOutputSqldaPtr != NULL) {
        int i = 0;
        for(; i < hOutputSqldaPtr->sqld; ++i) {
            free(hOutputSqldaPtr->sqlvar[i].sqldata);
            free(hOutputSqldaPtr->sqlvar[i].sqlind);
        }

        free(hOutputSqldaPtr);
    }
}

void askForParameterMarkerValues() {
    int i = 0;
    short sqltype, sqllen;
    for(; i < hInputSqldaPtr->sqld; ++i) {
        sqltype = hInputSqldaPtr->sqlvar[i].sqltype;
        sqllen = hInputSqldaPtr->sqlvar[i].sqllen;
        
        // Za svaku parametarsku oznaku na osnovu njegov tipa,
        // pitamo korisnika da unese vrednost
        printf("Unesite vrednost za %d. parametarsku oznaku: ", i+1);

        switch (sqltype) {
            case SQL_TYP_SMALL:
            case SQL_TYP_NSMALL:
                scanf("%hd", (short *)hInputSqldaPtr->sqlvar[i].sqldata);
                break;
                
            case SQL_TYP_INTEGER:
            case SQL_TYP_NINTEGER:
                scanf("%d", (int *)hInputSqldaPtr->sqlvar[i].sqldata);
                break;
                
            case SQL_TYP_FLOAT:
            case SQL_TYP_NFLOAT:
                scanf("%f", (float *)hInputSqldaPtr->sqlvar[i].sqldata);
                break;
                
            case SQL_TYP_DECIMAL:
            case SQL_TYP_NDECIMAL:
                scanf("%lf", (double *)hInputSqldaPtr->sqlvar[i].sqldata);
                break;
            
            case SQL_TYP_DATE:
            case SQL_TYP_TIME:
            case SQL_TYP_STAMP:
            case SQL_TYP_VARCHAR:
            case SQL_TYP_CHAR:
            case SQL_TYP_CSTR:
            case SQL_TYP_LONG:
                for (short j = 0; j < sqllen;) {
                    char c = getchar();
                    if (c == '\n') {
                        if (j == 0) {
                            continue;
                        }
                        break;
                    }
                    hInputSqldaPtr->sqlvar[i].sqldata[j] = c;
                    ++j;
                }
                break;
                
            case SQL_TYP_NDATE:
            case SQL_TYP_NTIME:
            case SQL_TYP_NSTAMP:
            case SQL_TYP_NVARCHAR:
            case SQL_TYP_NCHAR:
            case SQL_TYP_NCSTR:
            case SQL_TYP_NLONG:
                for (short j = 0; j < sqllen;) {
                    char c = getchar();
                    if (c == '\n') {
                        if (j == 0) {
                            continue;
                        }
                        break;
                    }
                    hInputSqldaPtr->sqlvar[i].sqldata[j] = c;
                    ++j;
                }
                break;
                
            default:
                printf("Nepoznat tip: %hd\n", sqltype);
                break;
        }
    }
}

void executeStatement() {
    // Ukoliko naredba nije SELECT, tj. broj kolona izlaza je 0,
    // mozemo koristiti EXECUTE naredbu.
    if(hOutputSqldaPtr->sqld == 0) {
        executeNotSelectStatement();
        return;
    }

    // U suprotnom, moramo obraditi naredbu SELECT pomocu kursora.
    executeSelectStatement();
}

void executeNotSelectStatement() {
    EXEC SQL EXECUTE pStatement USING DESCRIPTOR :*hInputSqldaPtr;
    IS_ERROR_WITH_LINE("Execute immediate");
}

void executeSelectStatement() {
    EXEC SQL DECLARE KURSOR CURSOR FOR pStatement;
    IS_ERROR_WITH_LINE("Declare");

    EXEC SQL OPEN KURSOR
        USING DESCRIPTOR :*hInputSqldaPtr;
    IS_ERROR_WITH_LINE("Open");

    int broj_reda = 1;
    
    for(;;) {
        EXEC SQL FETCH KURSOR 
            USING DESCRIPTOR :*hOutputSqldaPtr;
        IS_ERROR_WITH_LINE("Fetch");

        if (SQLCODE == 100) {
            break;
        }

        printf("\n# %3d >>\t", broj_reda);
        
        int i = 0;
        for(; i < hOutputSqldaPtr->sqld; ++i) {
            short sqltype = hOutputSqldaPtr->sqlvar[i].sqltype;
            
            switch (sqltype) {
                case SQL_TYP_SMALL:
                case SQL_TYP_NSMALL:
                    if (*(short*)(hOutputSqldaPtr->sqlvar[i].sqlind) < 0) {
                        printf("%s \t", "NULL");
                    }
                    else {
                        printf("%hd \t", *((short*)(hOutputSqldaPtr->sqlvar[i].sqldata)));
                    }
                    break;
                    
                case SQL_TYP_INTEGER:
                case SQL_TYP_NINTEGER:
                    if (*(short*)(hOutputSqldaPtr->sqlvar[i].sqlind) < 0) {
                        printf("%s \t", "NULL");
                    }
                    else {
                        printf("%d \t", *((sqlint32*)(hOutputSqldaPtr->sqlvar[i].sqldata)));
                    }
                    break;
                    
                case SQL_TYP_FLOAT:
                case SQL_TYP_NFLOAT:
                    if (*(short*)(hOutputSqldaPtr->sqlvar[i].sqlind) < 0) {
                        printf("%s \t", "NULL");
                    }
                    else {
                        printf("%f \t", *((float*)(hOutputSqldaPtr->sqlvar[i].sqldata)));
                    }
                    break;
                    
                case SQL_TYP_DECIMAL:
                case SQL_TYP_NDECIMAL:
                    if (*(short*)(hOutputSqldaPtr->sqlvar[i].sqlind) < 0) {
                        printf("%s \t", "NULL");
                    }
                    else {
                        double value = getDoubleFromDecimal(hOutputSqldaPtr->sqlvar[i].sqldata, hOutputSqldaPtr->sqlvar[i].sqllen);
                        printf("%lf", value);
                    }
                    break;
                
                case SQL_TYP_CSTR:
                case SQL_TYP_NCSTR:
                    if (*(short*)(hOutputSqldaPtr->sqlvar[i].sqlind) < 0) {
                        printf("%s \t", "NULL");
                    }
                    else {
                        hOutputSqldaPtr->sqlvar[i].sqldata[hOutputSqldaPtr->sqlvar[i].sqllen] = 0;
                        printf("%s \t", (char*)(hOutputSqldaPtr->sqlvar[i].sqldata));
                    }
                    break;
                    
                default:
                    printf("Nepoznat tip: %hd", sqltype);
                    break;
            }
        }
        
        ++broj_reda;
    }    
    
    printf("\n");

    EXEC SQL CLOSE KURSOR;
    IS_ERROR_WITH_LINE("Close");

    EXEC SQL COMMIT;
    IS_ERROR_WITH_LINE("Commit");
}

double getDoubleFromDecimal(char *sqldata, short sqllen) {
    char buf[1000];
    short precision = (short)(((char *)&sqllen)[0]);
    short scale = (short)(((char *)&sqllen)[1]);

    // Prilagodi preciznost na neparnu vrednost
    if ((precision % 2) == 0) {
        precision = precision + 1;
    }

    short numBytes = (short)(precision + 1) / 2;

    // Izracunavanje znaka na osnovu donje vrednost poslednjeg bajta
    short bottom = *(sqldata + numBytes - 1) & 0x000F;
    int i = 0;
    if (bottom == 0x000D || bottom == 0x000B) {
        buf[i] = '-';
        i++;
    }
    else {
        buf[i] = ' ';
        i++;
    }

    // Pripremanje decimalnog broja
    short digNb = 0;
    if (digNb == precision - scale) {
        buf[i] = '.';
        i++;
    }

    short byteNb;
    short top;

    // (top + bottom) iz prvih (numBytes - 1) bajtova ...
    for (byteNb = 0; byteNb < numBytes - 1; byteNb = byteNb + 1) {
        top = *(sqldata + byteNb) & 0x00F0;
        top = top >> 4;
        bottom = *(sqldata + byteNb) & 0x000F;
        buf[i] = top + '0';
        i++;
        digNb++;
        if (digNb == precision - scale) {
            buf[i] = '.';
            i++;
        }
        buf[i] = bottom + '0';
        i++;
        digNb++;
        if (digNb == precision - scale) {
            buf[i] = '.';
            i++;
        }
    }

    // ... i pocetak poslednjeg bajta (kraj predstavlja znak)
    top = *(sqldata + byteNb) & 0x00F0;
    top = top >> 4;
    buf[i] = top + '0';
    i++;
    digNb++;
    if (digNb == precision - scale) {
        buf[i] = '.';
        i++;
    }

    // Izracunaj decimalni broj 
    buf[i] = '\0';
    return atof(buf);
}

void checkSQL(const char *str, int linenum) {
    if(sqlca.sqlcode < 0) {
        char buffer[1024];
        short bufferSize = sizeof(buffer);
        short lineWidth = 50;
        sqlaintp(buffer, bufferSize, lineWidth, &sqlca);

        fprintf(stderr, "Greska %d: %s\n", sqlca.sqlcode, str);
        fprintf(stderr, "%s\n", buffer);
        
        exitGracefully();
    }
}

void exitGracefully() {
    freeSqlda();
    
    EXEC SQL ROLLBACK;
    EXEC SQL CONNECT RESET;
    exit(EXIT_FAILURE);
}

4.8 Zadaci za vežbu

Zadatak 4.5: Napisati C/SQL program u kojem se naredbe izvršavaju dinamički. Izdvojiti naziv predmeta, prosečnu ocenu i procenat studenata iz tog predmeta u školskoj godini koja se unosi sa standardnog ulaza.

Zadatak 4.6: Uraditi zadatke za vežbu iz poglavlja 2 i 3 korišćenjem dinamičkih SQL naredbi.