Prikaži sadržaj

Programiranje baza podataka

6. Aplikacije u višekorisničkom okruženju

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.

Ovo poglavlje je posvećeno dizajnu aplikacija koje koriste baze podataka, a za koje se očekuje da će biti izvršavane u višekorisničkom okruženju. Ovo podrazumeva da je potrebno uvesti dodatne mere u slučaju da više aplikacija želi da pristupi nekom objektu u bazi podataka (na primer, određenom redu ili tabeli).

6.1 Aplikacioni proces i konkurentnost

Svi SQL-aplikativni programi se izvršavaju kao deo aplikacionih procesa ili agenata. Aplikacioni proces podrazumeva izvršavanje jednog ili više programa, i predstavlja jedinicu kojoj SUBP alocira resurse i katance. Različiti aplikacioni procesi mogu da povlače sa sobom izvršavanje različitih programa ili različita izvršavanja istog programa.

U višekorisničkom okruženju, osnovna pretpostavka je da više od jednog aplikacionog procesa može da zahteva pristup istim podacima u isto vreme. U tu svrhu, da bi se obezbedio integritet podataka, potrebno je implementirati i koristiti mehanizam poznat pod nazivom zaključavanje.

Zaključavanje (engl. locking) predstavlja mehanizam zasnovan na katancima koji se koristi za održavanje integriteta podataka pod uslovima višekorisničkog okruženja.

Korišćenjem zaključavanja se može sprečiti da, na primer, dva aplikaciona procesa izvrše ažuriranje istog reda u isto vreme.

SUBP upravlja katancima da bi onemogućio da nepotvrđene izmene napravljene od strane jednog aplikacionog procesa budu vidljive od bilo kog drugog procesa. U trenutku kada se neki proces završi, tada se svi katanci koje je proces dobio od strane RSUBP, i držao ih, oslobađaju. Ipak, aplikacioni proces može da eksplicitno zahteva da se neki katanac oslobodi ranije. Ovo je omogućeno korišćenjem operacije potvrđivanja izmena, koja oslobađa katance koji su dobijeni tokom jedinice posla i koja, takođe, potvrđuje sve izmene koje su obavljene tokom te jedinice posla.

Direktni pozivi DB2 funkcija i ugnežđeni SQL omogućavaju režim konekcije koji se naziva konkurentne transakcije (engl. concurrent transactions) kojim se omogućavaju višestruke konekcije ka bazama podataka, pri čemu svaka od njih predstavlja nezavisnu transakciju.

Aplikacija može da ima više konkurentnih konekcija ka istoj bazi podataka.

6.2 Katanci

Kao što smo rekli, katanci predstavljaju osnovni alat za uspostavljanje mehanizma zaključavanja. Svaki katanac ima odgovarajuće karakteristike:

DB2 sistem nudi podršku za različite režime katanaca. Mi ćemo prikazati samo najosnovnije, poređane rastuće po restrikciji koju obezbeđuju:

Očigledno, primenom različitih vrsta katanaca, može doći do sukoba u smislu da li SUBP treba da dodeli katanac nekom aplikacionom procesu kada postoji drugi aplikacioni proces koji već drži katanac nad istim objektom. Samo u slučaju kada su katanci kompatibilni će SUBP dodeliti drugi katanac.

U slučaju da katanci nisu kompatibilni, nije moguće dodeliti traženi katanac aplikacionom procesu. Tada taj proces mora da pređe u stanje čekanja dok proces ne oslobodi nekompatibilni katanac koji drži, kao i dok se svi ostali nekompatibilni katanci ne oslobode. U nekim slučajevima može doći do isteka vremena (engl. timeout) dok zatražilac čeka na katanac. O tome će nešto detaljnije biti reči u nastavku, a za sada ćemo prikazati kompatibilnosti između katanaca u narednoj tabeli. Oznaka N znači da nad resursom nema katanca, odnosno, da aplikacioni proces ne traži katanac.

"Tabela kompatibilnosti katanaca."

6.2.1 Konverzija i ekskalacija katanaca

Konverzija katanca se ostvaruje u situacijama kada aplikacioni proces pristupa objektu nad kojim već drži katanac, a taj pristup zahteva restriktivniji katanac od onog koji se već drži. Proces nad objektom može da drži najviše jedan katanac u nekom trenutku, ali može da traži različite katance nad istim objektom indirektno kroz izvršavanje naredbi.

Promena režima katanca koji se već drži se naziva konverzija katanca (engl. lock conversion).

Neki režimi katanaca se primenjuju samo za tabele, neki samo za redove, blokove ili particije podataka. Za redove ili blokove, konverzija se uglavnom izvršava ukoliko se traži katanac X, a već se drži katanac S ili U.

Katanci IX i S imaju specijalan odnos — nijedan od ova dva katanca se smatra restriktivnijim u odnosu na drugi. Ukoliko aplikacioni proces drži jedan od ova dva katanca i zahteva drugi, onda će se izvršiti konverzija u katanac SIX. Sve ostale konverzije se vrše po narednom principu: Ukoliko je katanac koji se zahteva restriktivniji u odnosu na katanac koji se drži, onda se katanac koji se drži konvertuje u katanac koji se zahteva.

Naravno, da bi se uspešno izvršila konverzija katanca, nad objektom nad kojim se zahteva novi katanac ne sme postojati neki drugi katanac koji je nekompatibilan sa njim.

Da bi se uslužio što veci broj aplikacija, menadžer baza podataka omogucava funkciju ekskalacije katanaca. Ovaj proces objedinjuje proces dobijanja katanca na tabeli i oslobađanja katanaca na redovima. Željeni efekat je da se smanje ukupni zahtevi skladištenja za katance od strane menadžera baza podataka. Ovo ́ce omoguciti drugim aplikacijama da dobiju željene katance. Dva konfiguraciona parametra baze podataka imaju direktan uticaj na proces ekskalacije katanaca:

Oba parametra su konfigurabilna.

Ekskalacija katanaca se dešava u dva slučaja:

Ekskalacija katanaca može da ne uspe. Ako se desi greška, aplikacija koja je pokrenula ekskalaciju dobiće vrednost SQLCODE -912. Ovaj kod bi trebaloda bude obrađen programski od strane aplikacije.

6.2.2 Istek vremena i mrtva petlja

Već smo napomenuli da, ukoliko aplikacija zahteva katanac nad objektom koji je nekompatibilan sa katancem koji već postoji nad istim objektom, ona ulazi u fazu čekanja dok se taj katanac ne oslobodi. Neka, na primer, jedna transakcija čeka na katanac koji drži neka korisnička aplikacija. Ukoliko korisnik koji koristi tu aplikaciju napusti aplikaciju bez da dozvoli potvrđivanje izmena, onda se katanac ne oslobađa nad tim objektom i transakcija može da čeka beskonačno dugo.

SUBP ima poseban mehanizam kojim se može sprečiti da aplikacija beskonačno dugo čeka na oslobađanje katanca i koji se naziva detekcija isteka vremena (engl. lock timeout detection). Ovaj mehanizam se oslanja na korišćenje locktimeout parametra podešavanja baze podataka i predstavlja najduže vreme koje transakcija može da čeka na oslobađanje katanca. Podrazumevana vrednost ovog parametra je -1, što znači da je detekcija isteka vremena onemogućena. U nastavku ćemo videti kako možemo postaviti vrednost ovog parametra, kao i koje je ponašanje SUBP u slučaju da dođe do isteka vremena, međutim, prvo ćemo diskutovati o situaciji do koje može doći u slučaju da aplikacije mogu da čekaju beskonačno dugo na oslobađanje katanca.

Pretpostavimo da postoje dve aplikacije A i B koje rade konkurentno i da je mehanizam detekcije isteka vremena onemogućen u SUBP. Aplikacija A se sastoji od dve operacije: prva operacija želi da ažurira red 1 u tabeli 1, a druga operacija želi da ažurira red 2 u tabeli 2. Aplikacija B se takođe sastoji od dve operacije: prva operacija želi da ažurira red 2 u tabeli 2, a druga operacija želi da ažurira red 1 u tabeli 1. Pretpostavimo takođe da se operacije dešavaju u (približno) isto vreme. Redosled izvršavanja ovih operacija i dobijanja katanaca je sledeći (što je ilustrovano i na narednoj slici):

"Ilustracija pojave mrtve petlje prilikom izvršavanja dva aplikaciona procesa."

Očigledno, počevši od trenutka T3, obe aplikacije će čekati jedna na drugu beskonačno dugo i neće se “nikad” završiti. Ova pojava se naziva mrtva petlja.

Mrtva petlja (engl. deadlock) predstavlja pojavu kada dve aplikacije (ili više njih) zaključaju podatak koji je neophodan jedan drugoj, što rezultuje u situaciji da se obe aplikacije blokiraju i nijedna ne može da nastavi sa daljim izvršavanjem.

Zbog toga što aplikacije neće “volonterski” osloboditi katance koje drže nad podacima, da bi se prevazišla mrtva petlja, mora se pristupiti procesu prepoznavanja mrtve petlje. Mehanizam prepoznavanja mrtve petlje nadgleda informacije o agentima koji čekaju na katance i pokreće se na interval specifikovan kroz parametar dlchktime podešavanja baze podataka.

Ukoliko mehanizam pronađe mrtvu petlju, onda se nasumično bira jedan od procesa u mrtvoj petlji koji se naziva žrtva (engl. victim process), čije će izmene biti poništene. Aplikacija koja pripada procesu žrtvi se podiže i prosleđuje joj se odgovarajući kod koji je ona dužna da obradi. SUBP automatski poništava nepotvrđene izmene od strane odabrane aplikacije. Kada je operacija poništavanja završena, katanci koji su pripadali procesu žrtvi se oslobađaju, kursori se zatvaraju i ostali procesi u mrtvoj petlji mogu da nastave dalje sa radom.

Postoje dve vrste grešaka koje SUBP može prijaviti aplikaciji u slučaju da dođe do isteka vremena, odnosno, ako je aplikacija izabrana za žrtvu prilikom prepoznavanja mrtve petlje:

Pokretanjem komande db2 ? sql911 u terminalu je moguće dobiti više informacija o grešci.

Pokretanjem komande db2 ? sql913 u terminalu je moguće dobiti više informacija o grešci.

Još jedan mogući način da se izborimo sa mrtvom petljom jeste da specifikujemo najduži vremenski period koji aplikacija može da čeka na dobijanje katanca. Specijalni registar CURRENT LOCK TIMEOUT, koji je tipa INTEGER, sadrži broj sekundi pre nego što SUBP vrati grešku koja indikuje da katanac ne može biti dodeljen aplikacionom procesu.

Validne vrednosti za ovaj registar su u opsegu [-1, 32767]. Dodatno, specijalnom registru se može postaviti vrednost NULL. Vrednost -1 označava da SUBP neće prijavljivati istek vremena, već da aplikacija mora da čeka na zahtevani katanac dok se nekompatibilni katanac ne oslobodi ili dok se ne detektuje mrtva petlja. Vrednost 0 označava da aplikacija neće čekati na katanac, već ukoliko ne može da dobije katanac kada ga zatraži, odmah će dobiti grešku. Vrednost NULL označava da će SUBP koristiti vrednost koja je postavljena u parametru locktimeout o kojem je bilo reči ranije.

Ukoliko želimo da pročitamo vrednost ovog specijalnog registra, nakon konekcije na bazu podataka potrebno je izvršiti narednu komandu u terminalu:

db2 values CURRENT LOCK TIMEOUT

SQL naredba SET CURRENT LOCK TIMEOUT služi za promenu vrednosti specijalnog registra CURRENT LOCK TIMEOUT. Izvršavanje ove naredbe nije pod uticajem kontrole transakcije u programu. Zbog toga, ukoliko ovu naredbu koristimo u našim aplikacijama, dobra je praksa vratiti vrednost na podrazumevanu pre završavanja programa. Sintaksa ove naredbe je data u nastavku:

SET [CURRENT] LOCK TIMEOUT [=]
(WAIT|NOT WAIT|NULL|[WAIT] <CELOBROJNA_KONSTANTA>|<MATIČNA_PROMENLJIVA>)

Ovoj naredbi je moguće specifikovati naredne vrednosti:

Zadatak 6.1: Napisati C/SQL program koji za svaki studijski program pita korisnika da li želi da promeni obim ESPB bodova na tom studijskom programu. Ukoliko korisnik odgovori potvrdno, aplikacija zahteva od korisnika da unese novi broj bodova nakon čega se vrši izmena podataka.

Napisati program tako da može da radi u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Pokrenuti dve instance aplikacije. Uveriti se da dok jedna aplikacija obrađuje tekući studijski program, druga aplikacija ne može da dobije katanac nad odgovarajućim slogom.

Rešenje: Nakon izvršavanja svake SQL naredbe koja može da zahteva zaključavanje nekog sloga u tabeli, potrebno je izvršiti proveru da li aplikacija može da dobije odgovarajući katanac. Provera se sastoji u ispitivanju vrednosti makroa SQLCODE i to pre poziva funkcije za obradu greške, kako se proces ne bi završio pre obrade katanaca. Sama obrada čekanja je u ovom slučaju vrlo jednostavna - potrebno je ispisati poruku korisniku da je isteklo vreme za čekanje trenutnog katanca i nastaviti dalje sa izvršavanjem (poništiti eventualne izmene i ponovo otvoriti kursor koji se obrađuje).

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_1.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
char hStudyProgramName[201];
short hStudyProgramESPB;
EXEC SQL END DECLARE SECTION;

// Definisemo funkciju koja ce vrsiti obradu cekanja u slucaju da dodje do problema u visekorisnickom okruzenju.
// Primetimo da je definicija funkcije odvojena od njene deklaracije,
// zato sto se u definiciji ove funkcije koristi (otvara) kursor `cStudyPrograms`.
// Ako bismo stavili definiciju ove funkcije ispred `main` funkcije,
// onda bi Db2 pretprocesor izdao upozorenje da se kursor koristi (otvara) pre deklaracije.
// Funkcija vraca 1 ukoliko je doslo do problema, a 0 inace.
int waitForLock();

// Funkcija za obradu SQL gresaka
void checkSQL(const char *str);

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    // Postavljanje isteka vremena na 5 sekundi
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set current lock timeout 5");
    
    EXEC SQL 
        DECLARE cStudyPrograms CURSOR WITH HOLD FOR 
        SELECT  TRIM(NAZIV), 
                OBIMESPB
        FROM    DA.STUDIJSKIPROGRAM
        FOR     UPDATE OF OBIMESPB;
    checkSQL("Declare cStudyPrograms");
    
    EXEC SQL OPEN cStudyPrograms;
    checkSQL("Open cStudyPrograms");

    for(;;) {
        // Naredba FETCH dohvata jedan red iz rezultata upita.
        // Bilo da li se informacije koriste za citanje ili za eventualnu izmenu,
        // SUBP mora da dodeli aplikaciji odgovarajuci katanac.
        EXEC SQL 
            FETCH   cStudyPrograms 
            INTO    :hStudyProgramName, 
                    :hStudyProgramESPB;
        if (waitForLock()) {
            continue;
        }
        checkSQL("Fetch cStudyPrograms");

        if (SQLCODE == 100) {
            printf("Nema vise studijskih programa za obradjivanje!\n");
            break;
        }
            
        printf("%s (%hd)\n", hStudyProgramName, hStudyProgramESPB);

        printf("Da li zelite da promenite bodove za ovaj studijski program? [d/n] ");
        char userResponse = getchar();
        getchar();  // novi red

        if (userResponse != 'd') {
            continue;
        }
        
        printf("Unesite broj bodova: ");
        scanf("%hd", &hStudyProgramESPB);

        // Izvrsavanje naredbe UPDATE uvek trazi katanac za azuriranje podataka
        EXEC SQL 
            UPDATE  DA.STUDIJSKIPROGRAM
            SET     OBIMESPB = :hStudyProgramESPB
            WHERE   CURRENT OF cStudyPrograms;
        if (waitForLock()) {
            continue;
        }
        checkSQL("Update");

        printf("Bodovi su azurirani!\n\n");
    }

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

    // Vracamo istek vremena na podrazumevanu vrednost
    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set current lock timeout null");

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

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

    return 0;
}

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock() {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n");
        
        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        EXEC SQL OPEN cStudyPrograms;
        checkSQL("Open cStudyPrograms - waitForLock");

        return 1;
    }
    return 0;
}

6.3 Obrada transakcija u višekorisničkom okruženju

Problem sa pristupom obrade grešaka u prethodnom zadatku je taj što se pod transakcijom smatra izvršavanje celog programa, te ako bilo gde u obradi dođe do problema dobijanja katanaca, sve izmene koje su do tada načinjene se poništavaju. Umesto toga, pouzdanija tehnika jeste definisati da jedna transakcija obuhvata obradu jednog podatka. U prethodnom primeru, to bi značilo da jedna transakcija obuhvata izmenu tačno jednog studijskog programa, a ne svih studijskih programa. Dodatno, često je poželjno da aplikacija pamti koji podaci su već obrađeni, kako ih ne bi obrađivala dva puta. Ovo je važno pamtiti zbog toga što otvaranjem kursora u obradi čekanja, kursor se pozicionira na početak rezultujuće tabele, a ne tamo gde je obrada stala. (Alternativno, možemo pamtiti broj obrađenih redova u neku matičnu promenljivu row_count, pa zatim koristiti klauzu OFFSET :row_count ROWS u SELECT naredbi.)

Naredni zadatak ilustruje obradu transakcija u višekorisničkom okruženju korišćenjem znanja iz ovog poglavlja.

Zadatak 6.2: Napisati C/SQL program koji za svaki predmet omogućava korisniku da poveća broj ESPB za 1. Nakon svakog ažuriranja, proveriti da li je odgovarajuci red u tabeli izmenjen ponovnim dohvatanjem informacija.

Napisati program tako da može da radi u višekorisničkom okruženju. Obrada jednog predmeta predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 10 sekundi.

Rešenje: S obzirom da se ovoga puta obrada katanaca vrši kao deo zasebne transakcije, onda je potrebno da poništavamo izmene ukoliko dođe do isteka vremena za dobijanje katanaca. Zbog toga, funkciji za obradu čekanja dodajemo naredbu ROLLBACK. Dodatno, kako će ta naredba zatvoriti sve otvorene kursore u transakciji (pa čak i one deklarisane klauzom WITH HOLD), potrebno je otvoriti kursor koji se koristio pre nego što se pređe u narednu iteraciju petlje koja obrađuje taj kursor.

Takođe, primetimo da aplikacija čuva niz obrađenih predmeta tokom svog rada. Prilikom obrađivanja narednog predmeta, aplikacija prvo proverava funkcijom int isCourseProcessed(sqlint32 id) i ukoliko jeste, aplikacija ga preskače i prelazi na naredni predmet. U suprotnom, vrši se obrada i funkcijom void setCourseAsProcessed(sqlint32 id) se predmet označava da je obrađen za naredne provere.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_2.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[21], 
     hCourseName[151];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;

// Niz identifikatora predmeta obradjenih od strane ovog programa.
// Ovde koristimo staticki niz radi ilustracije, ali broj predmeta nije poznat unapred, naravno,
// pa bi korektnije resenje koristilo dinamicki inicijalizovan niz.
sqlint32 processedCoursesArr[1000];
size_t processedCoursesArrSize = 0u;

// Pomocne funkcije sa rad sa nizom identifikatora predmeta
int isCourseProcessed(sqlint32 id);
void setCourseAsProcessed(sqlint32 id);

// Pomocne funkcije za obradu gresaka i cekanja na katance
void checkSQL(const char *str);
int waitForLock();

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set current lock timeout 5");
    
    EXEC SQL 
        DECLARE cCourses CURSOR WITH HOLD FOR 
        SELECT  ID, 
                TRIM(OZNAKA), 
                TRIM(NAZIV), 
                ESPB
        FROM    DA.PREDMET
        FOR     UPDATE OF ESPB;
    checkSQL("Declare cCourses");
    
    EXEC SQL OPEN cCourses;
    checkSQL("Open cCourses");
    
    printf("PREDMETI\n");

    for(;;) {
        EXEC SQL 
            FETCH   cCourses 
            INTO    :hCourseId, 
                    :hCourseLabel, 
                    :hCourseName, 
                    :hCourseESPB;
        if (waitForLock()) {
            continue;
        }
        checkSQL("Fetch cCourses");

        if (SQLCODE == 100) {
            printf("Nema vise predmeta za obradjivanje!\n");
            break;
        }

        // Provera da li je predmet vec obradjen 
        // od strane ove instance aplikacije
        if (isCourseProcessed(hCourseId)) {
            continue;
        }
        
        printf("\n---------------------------------------------------------------\n");
        printf("%5.5d %-10.10s %-40.40s %5.5d", hCourseId, hCourseLabel, hCourseName, hCourseESPB);
        printf("\n---------------------------------------------------------------\n");

        printf("\nDa li zelite da uvecate broj ESPB za ovaj predmet za 1? [d/n] ");
        char userResponse = getchar();
        getchar();  // novi red

        if (userResponse == 'd') {
            // Azuriranje podataka zahteva katanac za menjanje podataka
            EXEC SQL 
                UPDATE  DA.PREDMET
                SET     ESPB = ESPB + 1
                WHERE   CURRENT OF cCourses;
            if (waitForLock()) {
                continue;
            }
            checkSQL("Update");

            // Slicno kao i FETCH, naredba SELECT INTO zahteva katanac za citanje
            EXEC SQL 
                SELECT  ESPB
                INTO    :hCourseESPB
                FROM    DA.PREDMET
                WHERE   ID = :hCourseId;
            if (waitForLock()) {
                continue;
            }
            checkSQL("Select into");

            printf("Broj ESPB je sada %hd\n", hCourseESPB);
        }

        // Evidentiramo izmene u nizu namenjenim za to.
        setCourseAsProcessed(hCourseId);
        
        // Potvrdjivanje izmena u tekucoj transakciji
        EXEC SQL COMMIT;
        checkSQL("Commit");
    }

    EXEC SQL CLOSE cCourses;
    checkSQL("Close cCourses");

    // Vracamo istek vremena na podrazumevanu vrednost
    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set current lock timeout null");

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

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

    return 0;
}

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock() {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n");

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        EXEC SQL OPEN cCourses;    
        checkSQL("Open cCourses - obrada cekanja");

        return 1;
    }
    return 0;
}

int isCourseProcessed(sqlint32 id) {
    for (unsigned i = 0; i < processedCoursesArrSize; ++i) {
        if (processedCoursesArr[i] == id) {
            return 1;
        }
    }
    return 0;
}

void setCourseAsProcessed(sqlint32 id) {
    processedCoursesArr[processedCoursesArrSize++] = id;
}

6.4 Nivoi izolovanosti

U najosnovnijem slučaju, svaka transakcija je u potpunosti izolovana od svih drugih transakcija. Međutim, videli smo da ovakva, potpuna izolovanost, može brzo dovesti do problema poput mrtve petlje u konkurentnom sistemu izvršavanja. Zbog toga, da bi se povećala konkurentnost između aplikacionih procesa, DB2 SUBP definiše različite nivoe izolovanosti na kojima aplikacija može da se izvršava.

Nivo izolovanosti (engl. isolation level) koji se pridružuje aplikacionom procesu određuje stepen zaključavanja ili izolacije u kojem su podaci kojima taj proces pristupa, u odnosu na druge konkurentne procese.

Drugim rečima, nivo izolovanosti jednog aplikacionog procesa P specifikuje sledeće:

DB2 definiše naredna četiri nivoa izolacije:

6.4.1 Ponovljeno čitanje

U nivou izolacije ponovljeno čitanje (engl. repeatable read, skr. RR) nijedan podatak koji je pročitan od strane procesa P ne sme se menjati od strane drugih procesa sve do završetka obrade od strane procesa P (čime se obezbeđuje saglasnost podataka u slučaju ponovljenog čitanja, te otuda i naziv nivoa izolacije). Sa druge strane, nijedan red promenjen od strane drugog procesa ne može se čitati od strane procesa P dok se promene u okviru tog procesa na okončaju.

Osim već pomenutih ekskluzivnih katanaca, proces na RR nivou izolovanosti dobija bar deljive katance na svim podacima kojima pristupa, pri čemu se zaključavanje vrši tako da je proces u potpunosti izolovan od strane ostalih procesa.

6.4.2 Stabilno čitanje

U nivou izolacije stabilno čitanje (engl. read stability, skr. RS) nijedan podatak koji je pročitan od strane procesa P ne sme se menjati od strane drugih procesa sve do završetka obrade od strane procesa P. Sa druge strane, nijedan red promenjen od strane drugog procesa ne može se čitati dok se promene u okviru tog procesa ne okončaju.

Međutim, za razliku od RR, na ovom nivou izolacije proces koji izvršava isti upit više puta, može dobiti neke nove redove u odgovorima u slučaju da drugi proces unese nove redove koji zadovoljavaju dati upit i potvrdi te izmene. Ovo predstavlja tzv. problem fantomskih redova (engl. fantom rows problem).

Osim ekskluzivnih katanaca, proces na RS nivou izolovanosti dobija bar deljive katance na svim podacima koji su prihvaćeni. Na primer, neka se u procesu P izvršava naredni upit:

SELECT  *
FROM    DA.STUDIJSKIPROGRAM
WHERE   IDNIVOA = 1

U slučaju RR nivoa izolovanosti izvršavanje ovog upita izazvaće zaključavanje svih redova tabele STUDIJSKIPROGRAM, a u slučaju RS nivoa izolovanosti zaključavanje se vrši samo nad onim redovima koji ispunjavaju uslov restrikcije IDNIVOA = 1. Zbog toga, ako neki drugi proces unese novi red koji zadovoljava datu restrikciju, u slučaju RR nivoa izolovanosti to neće biti moguće, dok će ponovo izvršavanje upita u slučaju RS nivoa izolovanosti proizvesti prikazivanje i tih novih redova.

6.4.3 Stabilni kursor

U nivou izolacije stabilni kursor (engl. cursor stability, skr. CS) nijedan proces ne može da menja podatke iz reda dok je ažurirajući kursor pozicioniran nad tim redom. Dakle, ovaj nivo izolovanosti obezbeđuje da konkurentni procesi ne mogu menjati samo trenutno aktivne redove otvorenih kursora posmatranog procesa P. Redovi koji su ranije pročitani mogu biti menjani. Takodje, nijedan red promenjen od strane drugog procesa ne može se čitati dok se promene u okviru tog procesa na okončaju.

Takođe, ovaj nivo izolovanosti zaključava sve redove kojima transakcija pristupa dok je kursor pozicioniran na tim redovima. Ako transakcija koristi podatke iz reda za čitanje, onda se ovaj katanac drži sve dok se ne dohvati naredni red u kursoru ili dok se transakcija ne završi. Međutim, ako je izmena izvršena nad nekim redom, onda se nad tim redom drži katanac sve do kraja transakcije.

Očito, i u ovom nivou izolacije moguć je problem fantomskih redova. Međutim, postoji još jedan problem koji se može javiti. Neka prvo proces A pročita red, pa zatim pređe na druge redove. Neka zatim proces B pročita i izmeni isti red i potvrdi svoje izmene. Ukoliko proces A ponovo pročita isti red, videće drugačije vrednosti u odnosu na prethodno čitanje. Ovaj problem se naziva problem neponovljivih čitanja (engl. non-repeatable reads problem).

Bez obzira na razne ekskluzivne katance, ovaj nivo obezbeđuje bar deljive katance na aktivnim redovima svih kursora.

6.4.4 Nepotvrđeno čitanje

I nivou izolacije nepotvrđeno čitanje (engl. uncommitted read, skr. UR) proces P može da pristupi nepotvrđenim izmenama od strane drugih procesa. Dodatno, nivo izolacije UR ne sprečava drugim procesima da pristupe redu koji čita proces P, osim ukoliko taj drugi proces ne pokušava da izmeni strukturu tabele ili da je obriše.

Za naredbe SELECT INTO ili FETCH nad kursorom koji služi samo za čitanje, ili podupite naredbi INSERT ili UPDATE, ili upite koji kao rezultat imaju skalarnu vrednost, ovaj nivo izolacije omogućava da:

Pored problema fantomskih redova i problema neponovljivih čitanja, postoji još jedan problem koji se može javiti u ovom nivou izolacije. Ako prvo proces A izmeni neku vrednost, zatim proces B pročita tu vrednost pre nego što je potvrđena, a zatim proces A poništi svoje izmene, onda proces B može raditi nad nekorektnim podacima. Ovaj problem se naziva problem pristupa nepotvrđenim podacima (engl. access to uncommitted data problem).

6.4.5 Programersko podešavanje nivoa izolovanosti

Moguće je specifikovati koji se nivo izolovanosti koristi i on je u efektu tokom trajanja jedinice posla. Postoji nekoliko načina za podešavanje nivoa izolovanosti:

WITH (
    RR [<KLAUZA_ZAKLJUČAVANJA>] |
    RS [<KLAUZA_ZAKLJUČAVANJA>] |
    CS |
    UR
)

gde je <KLAUZA_ZAKLJUČAVANJA> klauza kojom se specifikuje tip katanca koji aplikacija zahteva od SUBP, a čija je sintaksa:

USE AND KEEP (SHARE|UPDATE|EXCLUSIVE) LOCKS

WITH klauza se može koristiti u narednim naredbama: DECLARE CURSOR, pretražujuća UPDATE naredba, INSERT, SELECT, SELECT INTO i pretražujuća DELETE naredba.

SET [CURRENT] ISOLATION [=] (UR|CS|RR|RS|RESET)

Navođenjem vrednosti RESET će vrednost registra biti postavljena na praznu nisku, što znači da se neće koristiti ta vrednost za registar.

ISOLATION (CS|RR|RS|UR)

Ukoliko se ne specifikuje nivo izolacije, DB2 će podrazumevano koristiti nivo CS.

Naredni primeri ilustruju različite efekte iste aplikacije pri različitim nivoima izolovanosti. Primetimo da smo u narednim primerima koristili klauzu WITH za postavljanje nivoa izolovanosti.

Zadatak 6.3: Napisati:

  • C/SQL program repeatableRead koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Omogućiti da se prilikom oba ispisivanja dobijaju iste informacije.
  • C/SQL program insertIspitniRok koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Rešenje: Za potrebe prevođenja i pripremanja podataka u bazi, napravljena je Makefile datoteka čiji je sadržaj:

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/Makefile:

# Za prevodjenje programa pozvati: make
# Za pripremu baze pozvati: make db
# Za ciscenje artefakta pozvati: make clean

repeatableRead: repeatableRead.sqc insertIspitniRok
	./prevodjenje repeatableRead stud2020 student abcdef

insertIspitniRok: insertIspitniRok.sqc
	./prevodjenje insertIspitniRok stud2020 student abcdef

.PHONY: clean db

clean:
	rm -f *.bnd
	rm -f *.o
	rm -f *.c
	rm -f repeatableRead
	rm -f insertIspitniRok

db:
	db2 -tf pripremaBaze.sql || true

Prevođenje programa se vrši pozivom alata make, a moguće je i očistiti artefakte prevođenja pozivom make clean. Pre ilustracije rada programa repeatableRead, potrebno je pozvati make db koji će pripremiti informacije o ispitnim rokovima u bazu podataka na osnovu skripta pripremaBaze.sql čiji je sadržaj:

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/pripremaBaze.sql:

CONNECT TO STUD2020 USER student USING abcdef;

DELETE  FROM DA.ISPITNIROK
WHERE   SKGODINA = 2021;

DELETE  FROM DA.SKOLSKAGODINA
WHERE   SKGODINA = 2021;

INSERT  INTO DA.SKOLSKAGODINA
VALUES  (2021, '09/01/2021', '09/01/2022');

INSERT  INTO DA.ISPITNIROK 
VALUES  (2021, 'Jan21', 'Januar 2021', '01/01/2022', '01/11/2022'),
        (2021, 'Feb21', 'Februar 2021', '02/01/2022', '02/11/2022');

CONNECT RESET;

Pokrenuti program repeatableRead i započeti prvo ispisivanje. U toku prvog ispisivanja ili nakon njega, pokrenuti program insertIspitniRok, pa nastaviti sa daljim ispisivanjem ispitnih rokova u programu repeatableRead. Uveriti se da program insertIspitniRok ne može da izvrši unos sve dok program repeatableRead ne završi sa svim ispisivanjima i, takođe, da oba ispisivanja u tom programu imaju iste vrednosti.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/repeatableRead.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21], 
     hFinalsName[31],
     hFinalsStartDate[11],
     hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

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

    EXEC SQL 
        DECLARE cFinals CURSOR FOR
        SELECT  SKGODINA, 
                OZNAKAROKA, 
                NAZIV,
                DATPOCETKA,
                DATKRAJA
        FROM    DA.ISPITNIROK
        WHERE   SKGODINA = 2021
        FOR     READ ONLY
        WITH    RR;
    checkSQL("Declare cursor");

    int i = 1;
    for(; i<3; ++i)
    {
        printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
        getchar();
        
        EXEC SQL OPEN cFinals;
        checkSQL("Open");
        
        printf("\n---------------------------------------------------------\n");
        
        for(;;)
        {
            EXEC SQL 
                FETCH   cFinals
                INTO    :hSchoolYear, 
                        :hFinalsLabel, 
                        :hFinalsName,
                        :hFinalsStartDate,
                        :hFinalsEndDate;
            checkSQL("Fetch");

            if(SQLCODE == 100) {
                break;
            }

            printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
        }
        
        printf("\n---------------------------------------------------------\n");
        
        EXEC SQL CLOSE cFinals;    
        checkSQL("Close");
    }

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

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


Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/insertIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            INSERT  INTO DA.ISPITNIROK
            VALUES  (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
            
        if (waitForLock("INSERT")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Zadatak 6.4: Napisati:

  • C/SQL program readStability koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi, ali ne i da budu vidljive izmene pročitanih redova.
  • C/SQL program insertIspitniRok koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
  • C/SQL program updateIspitniRok koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile koja služi za pozivanje alata make na već opisan način.

Pokrenuti program readStability i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok, pa nastaviti sa daljim ispisivanjem ispitnih rokova. Uveriti se da program insertIspitniRok može da izvrši unos novog ispitnog roka tokom rada programa readStability i da se novi rok vidi prilikom drugog ispisivanja. Zatim, ponovo pokrenuti program readStability (nakon ponovnog pripremanja baze podataka pozivom make db), pa u toku prvog ispisivanja ili nakon njega, pokrenuti program updateIspitniRok. Uveriti se da program updateIspitniRok ne može da promeni informacije o ispitnim rokovima dok program readStability radi.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/readStability.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21], 
     hFinalsName[31],
     hFinalsStartDate[11],
     hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

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

    EXEC SQL 
        DECLARE cFinals CURSOR FOR
        SELECT  SKGODINA, 
                OZNAKAROKA, 
                NAZIV,
                DATPOCETKA,
                DATKRAJA
        FROM    DA.ISPITNIROK
        WHERE   SKGODINA = 2021
        FOR     READ ONLY
        WITH    RS;
    checkSQL("Declare cursor");

    int i = 1;
    for(; i<3; ++i)
    {
        printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
        getchar();
        
        EXEC SQL OPEN cFinals;
        checkSQL("Open");
        
        printf("\n---------------------------------------------------------\n");
        
        for(;;)
        {
            EXEC SQL 
                FETCH   cFinals
                INTO    :hSchoolYear, 
                        :hFinalsLabel, 
                        :hFinalsName,
                        :hFinalsStartDate,
                        :hFinalsEndDate;
            checkSQL("Fetch");

            if(SQLCODE == 100) {
                break;
            }

            printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
        }
        
        printf("\n---------------------------------------------------------\n");
        
        EXEC SQL CLOSE cFinals;    
        checkSQL("Close");
    }

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

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/insertIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            INSERT  INTO DA.ISPITNIROK
            VALUES  (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
            
        if (waitForLock("INSERT")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/updateIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            UPDATE  DA.ISPITNIROK
            SET     DATKRAJA = DATKRAJA + 3 DAYS
            WHERE   SKGODINA = 2021;
            
        if (waitForLock("UPDATE")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Zadatak 6.5: Napisati:

  • C/SQL program cursorStability koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi i da budu vidljive izmene pročitanih redova, ali ne i nepotvrđene izmene.
  • C/SQL program insertIspitniRok koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
  • C/SQL program updateIspitniRok koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile koja služi za pozivanje alata make na već opisan način.

Pokrenuti program cursorStability i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok, pa nastaviti sa daljim ispisivanjem ispitnih rokova. Uveriti se da program insertIspitniRok može da izvrši unos novog ispitnog roka tokom rada programa readStability i da se novi rok vidi prilikom drugog ispisivanja. Zatim, ponovo pokrenuti program readStability (nakon ponovnog pripremanja baze podataka pozivom make db), pa u toku prvog ispisivanja ili nakon njega, pokrenuti program updateIspitniRok. Uveriti se da program updateIspitniRok ovoga puta može da promeni informacije o ispitnim rokovima, čak i dok program cursorStability radi, kao i da će ovoga puta program cursorStability videti te izmene prilikom drugog ispisivanja, što nije bio slučaj za nivo izolovanosti RS ili strožijim od njega.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/cursorStability.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21], 
     hFinalsName[31],
     hFinalsStartDate[11],
     hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

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

    EXEC SQL 
        DECLARE cFinals CURSOR FOR
        SELECT  SKGODINA, 
                OZNAKAROKA, 
                NAZIV,
                DATPOCETKA,
                DATKRAJA
        FROM    DA.ISPITNIROK
        WHERE   SKGODINA = 2021
        FOR     READ ONLY
        WITH    CS;
    checkSQL("Declare cursor");

    int i = 1;
    for(; i<3; ++i)
    {
        printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
        getchar();
        
        EXEC SQL OPEN cFinals;
        checkSQL("Open");
        
        printf("\n---------------------------------------------------------\n");
        
        for(;;)
        {
            EXEC SQL 
                FETCH   cFinals
                INTO    :hSchoolYear, 
                        :hFinalsLabel, 
                        :hFinalsName,
                        :hFinalsStartDate,
                        :hFinalsEndDate;
            checkSQL("Fetch");

            if(SQLCODE == 100) {
                break;
            }

            printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
        }
        
        printf("\n---------------------------------------------------------\n");
        
        EXEC SQL CLOSE cFinals;    
        checkSQL("Close");
    }

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

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/insertIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            INSERT  INTO DA.ISPITNIROK
            VALUES  (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
            
        if (waitForLock("INSERT")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/updateIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            UPDATE  DA.ISPITNIROK
            SET     DATKRAJA = DATKRAJA + 3 DAYS
            WHERE   SKGODINA = 2021;
            
        if (waitForLock("UPDATE")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Zadatak 6.6: Napisati:

  • C/SQL program uncommittedRead koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi, da budu vidljive izmene pročitanih redova, bilo one potvrđene ili ne.
  • C/SQL program insertIspitniRok koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Zahtevati od korisnika eksplicitno potvrđivanje izmena nakon unosa.
  • C/SQL program updateIspitniRok koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Zahtevati od korisnika eksplicitno potvrđivanje izmena nakon ažuriranja.

Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile koja služi za pozivanje alata make na već opisan način.

Pokrenuti program uncommittedRead i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok, ali ne potvrditi izmene još uvek. Zatim, nastaviti sa daljim ispisivanjem ispitnih rokova u programu uncommittedRead. Uveriti se da program insertIspitniRok može da izvrši unos novog ispitnog roka tokom rada programa uncommittedRead i da se novi rok vidi prilikom drugog ispisivanja, bez obzira što izmene u programu insertIspitniRok nisu potvrđene. Zatim, ponovo pokrenuti program readStability (nakon ponovnog pripremanja baze podataka pozivom make db) i uraditi isti postupak sa programom updateIspitniRok umesto insertIspitniRok. Uveriti se će i ovoga puta program uncommittedRead videti nepotvrđene izmene od strane programa updateIspitniRok, slično kao i u slučaju programa insertIspitniRok. Ovo je ponašanje koje se neće javiti u slučaju nivoa izolovanosti CS ili strožijim od njega.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/uncommittedRead.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21], 
     hFinalsName[31],
     hFinalsStartDate[11],
     hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

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

    EXEC SQL 
        DECLARE cFinals CURSOR FOR
        SELECT  SKGODINA, 
                OZNAKAROKA, 
                NAZIV,
                DATPOCETKA,
                DATKRAJA
        FROM    DA.ISPITNIROK
        WHERE   SKGODINA = 2021
        FOR     READ ONLY
        WITH    UR;
    checkSQL("Declare cursor");

    int i = 1;
    for(; i<3; ++i)
    {
        printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
        getchar();
        
        EXEC SQL OPEN cFinals;
        checkSQL("Open");
        
        printf("\n---------------------------------------------------------\n");
        
        for(;;)
        {
            EXEC SQL 
                FETCH   cFinals
                INTO    :hSchoolYear, 
                        :hFinalsLabel, 
                        :hFinalsName,
                        :hFinalsStartDate,
                        :hFinalsEndDate;
            checkSQL("Fetch");

            if(SQLCODE == 100) {
                break;
            }

            printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
        }
        
        printf("\n---------------------------------------------------------\n");
        
        EXEC SQL CLOSE cFinals;    
        checkSQL("Close");
    }

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

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/insertIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            INSERT  INTO DA.ISPITNIROK
            VALUES  (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
            
        if (waitForLock("INSERT")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    printf("\nPritisnuti enter za potvrdjivanje izmena: ");
    getchar();
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/updateIspitniRok.sqc:

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

EXEC SQL INCLUDE SQLCA;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        return 1;
    }
    return 0;
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("Set timeout");
    
    for (;;) {
        EXEC SQL 
            UPDATE  DA.ISPITNIROK
            SET     DATKRAJA = DATKRAJA + 3 DAYS
            WHERE   SKGODINA = 2021;
            
        if (waitForLock("UPDATE")) {
            continue;
        }
        
        checkSQL("Insert");
        
        if (SQLCODE == 0) {
            break;
        }
    };

    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("Set timeout");
    
    printf("\nPritisnuti enter za potvrdjivanje izmena: ");
    getchar();
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Sledi još jedan zadatak koji ilustruje koncept konkurentnih transakcija u višekorisničkom okruženju. Za potrebe ovog zadatka je neophodno izvršiti naredne SQL naredbe:

CREATE TABLE DA.DRZAVA (
    IDDRZAVE INTEGER NOT NULL PRIMARY KEY,
    NAZIV VARCHAR(50) NOT NULL
);

CREATE TABLE DA.EKSKURZIJA (
    INDEKS INTEGER NOT NULL,
    IDDRZAVE INTEGER NOT NULL,
    TRENUTAKPRIJAVE TIMESTAMP NOT NULL,
    PRIMARY KEY (INDEKS),
    FOREIGN KEY (INDEKS) REFERENCES DA.DOSIJE,
    FOREIGN KEY (IDDRZAVE) REFERENCES DA.DRZAVA
);

INSERT  INTO DA.DRZAVA
VALUES  (1, 'Spanija'),
        (2, 'Italija'),
        (3, 'Grcka'),
        (4, 'Nemacka'),
        (5, 'Rusija'),
        (6, 'Francuska');

Zadatak 6.7: Napisati C/SQL program koji od korisnika zahteva da unese identifikator studijskog programa sa osnovnih studija. Program na osnovu unetog podatka pronalazi naredne informacije o studentima sa datog studijskog programa: (1) broj indeksa, (2) ime, (3) prezime, (4) broj položenih ispita tog studenta, (5) broj položenih ESPB bodova, ali samo ukoliko student ima položeno bar 12 predmeta, ima skupljeno bar 120 bodova i ako se studentov indeks već ne nalazi u tabeli EKSKURZIJA.

Za svakog pronađenog studenta, korisnik unosi ceo broj koji predstavlja jedan od identifikatora država iz tabele DRZAVA ili 0 (pretpostaviti da je ispravan unos). Ukoliko korisnik unese identifikator, aplikacija unosi informacije u tabelu EKSKURZIJA, a ukoliko unese 0, prelazi se na sledećeg studenta. Nakon svaka 3 uneta glasa, ponuditi korisniku mogućnost da prekine sa daljom obradom i napusti program, unošenjem odgovora 'da'.

Napomene: Obrada jednog studenta predstavlja jednu transakciju. Proveravati greške koje se javljaju prilikom izvršavanja aplikacije u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Voditi računa da aplikacija ne sme da vidi bilo kakve vrste izmena od strane drugih aplikacija.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_7.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hStudyProgramId;

sqlint32 hIndex;
char hName[26];
char hSurname[26];
sqlint32 hNumOfPassedExams;
sqlint32 hSumESPB;

sqlint32 hCountryId;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str);
int waitForLock(const char *code_hint);

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("CONNECT TO");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT 5;
    checkSQL("CURRENT LOCK TIMEOUT 5");
    
    printf("Unesite identifikator studijskog programa sa osnovnih studija: ");
    scanf("%d", &hStudyProgramId);
    
    EXEC SQL DECLARE cStudents CURSOR WITH HOLD FOR 
        SELECT      D.INDEKS,
                    RTRIM(D.IME),
                    RTRIM(D.PREZIME),
                    COUNT(*) AS BROJ_POLOZENIH,
                    SUM(P.ESPB) AS BROJ_ESPB
        FROM        DA.DOSIJE D JOIN
                    DA.ISPIT I ON D.INDEKS = I.INDEKS JOIN
                    DA.PREDMET P ON I.IDPREDMETA = P.ID
        WHERE       D.IDPROGRAMA = :hStudyProgramId AND
                    I.OCENA > 5 AND
                    I.STATUS = 'o' AND
                    D.INDEKS NOT IN (
                        SELECT  INDEKS
                        FROM    DA.EKSKURZIJA
                    )
        GROUP BY    D.INDEKS,
                    D.IME,
                    D.PREZIME
        HAVING      COUNT(*) >= 12 AND
                    SUM(P.ESPB) >= 120
        FOR         READ ONLY
        WITH        RR;
    checkSQL("DECLARE");
        
    EXEC SQL OPEN cStudents;
    checkSQL("OPEN");
    
    unsigned numOfStudents = 0u;
    
    for(;;) {
        EXEC SQL FETCH cStudents INTO
            :hIndex,
            :hName,
            :hSurname,
            :hNumOfPassedExams,
            :hSumESPB;
        if (waitForLock("FETCH")) {
            continue;
        }
        checkSQL("FETCH");
        
        if (SQLCODE == 100) {
            break;
        }
        
        printf("\n----------------------------------------------------------------------------------------------------------------------\n");
        printf("Anketiram studenta: %-10.10s %-15.15s (%d) - broj polozenih ispita = %5.5d, broj polozenih espb = %5.5d", 
               hName, hSurname, hIndex, hNumOfPassedExams, hSumESPB);
        printf("\n----------------------------------------------------------------------------------------------------------------------\n");
        
        printf("\nUnesite identifikator drzave ili 0: ");
        scanf("%d", &hCountryId);
        
        if (!hCountryId) {
            continue;
        }
        
        printf("Unosim Vas glas za drzavu sa identifikatorom %d...\n", hCountryId);
        
        
        EXEC SQL
            INSERT  INTO DA.EKSKURZIJA
            VALUES  (:hIndex, :hCountryId, CURRENT_DATE);
        if (waitForLock("INSERT INTO")) {
            continue;
        }
        
        // Ako je neka druga aplikacija vec unela red za datog studenta...
        if (SQLCODE != -803) {
            checkSQL("INSERT INTO");
        }
        else {
            // ...onda cemo pitati korisnika da li zeli da radi azuriranje glasa.
            printf("Postoji glas za tekuceg studenta. Da li zelite da azurirate glas? [d/n] ");
            char odgovor = getchar();
            getchar();
            
            if (odgovor == 'n') {
                break;
            }
            
            EXEC SQL 
                UPDATE  DA.EKSKURZIJA
                SET     IDDRZAVE = :hCountryId
                WHERE   INDEKS = :hIndex;
            if (waitForLock("UPDATE")) {
                continue;
            }
            checkSQL("UPDATE");
        }
        
        printf("Vas glas je uspesno zabelezen!\n");
        
        EXEC SQL COMMIT;
        checkSQL("COMMIT");
        
        printf("\n********** END TRANSACTION **********\n");
        
        ++numOfStudents;
        
        if (!(numOfStudents % 3)) {
            printf("Da li zelite da zavrsite anketiranje? [da/ne] ");
            char userResponse[3];
            scanf("%s", userResponse);
            
            if (!strcmp(userResponse, "da")) {
                printf("Zavrsavam program!\n");
                break;
            }
        }
    }
    
    EXEC SQL CLOSE cStudents;
    checkSQL("CLOSE");
    
    EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
    checkSQL("CURRENT LOCK TIMEOUT NULL");
    
    EXEC SQL COMMIT;
    checkSQL("COMMIT - kraj programa");
    
    EXEC SQL CONNECT RESET;
    checkSQL("CONNECT RESET");
    
    return 0;
}

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int waitForLock(const char *code_hint) {
    if (-913 <= SQLCODE && SQLCODE <= -911) {
        printf("[%s] Objekat je zakljucan od strane druge transakcije. "
            "Sacekati neko vreme...\n", code_hint);

        EXEC SQL ROLLBACK;
        checkSQL("Rollback");

        EXEC SQL OPEN cStudents;    
        checkSQL("Open predmeti - obrada cekanja");

        return 1;
    }
    return 0;
}

6.5 Programersko zaključavanje tabela

Programeri mogu sami da zatraže katance od SUBP ukoliko smatraju da im je to neophodno, ali sa ograničenjem da je na ovaj način jedino moguće zaključati tabelu, a ne redove, blokove, i druge objekte. SQL naredba LOCK TABLE onemogućava konkurentnoj aplikaciji da koristi ili menja tabelu, u zavinosti od režima u kojem se tabela zaključava. Ovako dobijeni katanac se oslobađa kada se završi jedinica posla u kojem je naredba LOCK TABLE izvršena, bilo operacijom potvrđivanja izmena ili njenim završavanjem.

Korisnik koji izvršava naredbu LOCK TABLE mora da ima barem jednu od narednih privilegija da bi je uspešno izvršio:

Sintaksa ove naredbe je data u nastavku:

LOCK TABLE <IME_TABELE> IN [SHARE|EXCLUSIVE] MODE

Ovom naredbom se traži odgovarajući katanac nad tabelom <IME_TABELE>. U zavisnosti od odabranih vrednosti narednih opcija prilikom deklaracije složene SQL naredbe, ta naredba može imati različite varijante koji utiču na način izvršavanja:

Zadatak 6.6: Napisati C/SQL program shareMode koji ispisuje identifikator, oznaku, naziv i broj ESPB bodova za svaki studijski program u bazi podataka. Omogućiti da ostale aplikacije ne mogu da menjaju ove podatke tokom ispisivanja podataka.

Napisati C/SQL program exclusiveMode koji ispisuje identifikator, oznaku, naziv, broj semestara i broj ESPB bodova za svaki studijski program u bazi podataka. Omogućiti da ostale aplikacije ne mogu da menjaju ove podatke tokom ispisivanja podataka, kao ni da ih čitaju.

Rešenje: Isprobati sve kombinacije redosleda izvršavanja programa exclusiveMode i shareMode i uveriti se da jedino kombinacija (shareMode, shareMode) može raditi konkurentno. Primetimo da u ovom zadatku nismo koristili obradu čekanja na katance niti smo postavili vreme za istek katanaca, što znači da će jedna aplikacija čekati sve dok druga ne završi sa radom.

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_8/shareMode.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[11];
char hCourseName[201];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    // Zakljucavamo tabelu PREDMET u deljenom rezimu
    EXEC SQL LOCK TABLE DA.PREDMET IN SHARE MODE;
    checkSQL("Lock table");
    
    EXEC SQL 
        DECLARE cCourses CURSOR FOR 
        SELECT  ID, 
                OZNAKA, 
                NAZIV, 
                ESPB
        FROM    DA.PREDMET;
    checkSQL("Declare cursor");
    
    EXEC SQL OPEN cCourses;
    checkSQL("Open cursor");
    
    for(;;) {
        EXEC SQL 
            FETCH   cCourses
            INTO    :hCourseId, 
                    :hCourseLabel, 
                    :hCourseName, 
                    :hCourseESPB;
        checkSQL("Fetch cursor");
    
        if (SQLCODE == 100)
        {
            break;
        }

        printf("%4.4d %-10.10s %-30.30s %d\n", hCourseId, hCourseLabel, hCourseName, hCourseESPB);

        printf("Pritisnite ENTER za dalje citanje\n");
        getchar();
    }

    EXEC SQL CLOSE cCourses;
    checkSQL("Close cursor");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_8/exclusiveMode.sqc:

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

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[11];
char hCourseName[201];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL ROLLBACK;

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Connect");
    
    // Zakljucavamo tabelu PREDMET u privatnom rezimu
    EXEC SQL LOCK TABLE DA.PREDMET IN EXCLUSIVE MODE;
    checkSQL("Lock table");
    
    EXEC SQL 
        DECLARE cCourses CURSOR FOR 
        SELECT  ID, 
                OZNAKA, 
                NAZIV, 
                ESPB
        FROM    DA.PREDMET;
    checkSQL("Declare cursor");
    
    EXEC SQL OPEN cCourses;
    checkSQL("Open cursor");
    
    for(;;) {
        EXEC SQL 
            FETCH   cCourses
            INTO    :hCourseId, 
                    :hCourseLabel, 
                    :hCourseName, 
                    :hCourseESPB;
        checkSQL("Fetch cursor");
    
        if (SQLCODE == 100)
        {
            break;
        }

        printf("%4.4d %-10.10s %-30.30s %d\n", hCourseId, hCourseLabel, hCourseName, hCourseESPB);

        printf("Pritisnite ENTER za dalje citanje\n");
        getchar();
    }

    EXEC SQL CLOSE cCourses;
    checkSQL("Close cursor");
    
    EXEC SQL COMMIT;
    checkSQL("Potvrdjivanje izmena");

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

6.6 Zadaci za vežbu

Zadatak 6.9: Napisati C/SQL program koji za sve ispitne rokove pronalazi informacije o polaganjima za svaki položeni predmet u tom ispitnom roku i te podatke unosi u tabelu ISPITNIROKOVIPOLAGANJA. Nakon jednog unosa podataka ispisati unete podatke odvojene zapetom. Nakon svih unosa podataka, potrebno je odgovarajućom SQL naredbom izračunati broj unetih redova u tabeli ISPITNIROKOVIPOLAGANJA i ispisati rezultat na standardni izlaz. Napisati program tako da može da radi u višekorisničkom okruženju. Unos podataka za jedan ispitni rok i jedno polaganje predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Napraviti tabelu ISPITNIROKOVIPOLAGANJA koja sadrži kolone sa podacima o:

Definisati primarni ključ na osnovu prvih 5 kolona i strane ključeve ka odgovarajućim tabelama.

Zadatak 6.10: Napisati C/SQL program koji za sve studente pronalazi informacije o studijskom programu na kojem studiraju i te podatke unosi u tabelu STUDIJSKIPROGRAMPSTUDENTI. Nakon jednog unosa podataka ispisati unete podatke odvojene zapetom. Nakon svih unosa podataka, potrebno je odgovarajućom SQL naredbom izračunati broj unetih redova u tabeli STUDIJSKIPROGRAMPSTUDENTI i ispisati rezultat na standardni izlaz. Napisati program tako da može da radi u višekorisničkom okruženju. Unos podataka za jednog studenta i jedan studijski program predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Napraviti tabelu STUDIJSKIPROGRAMPSTUDENTI koja sadrži kolone sa podacima o:

Definisati primarni ključ na osnovu prve 2 kolone i strane ključeve ka odgovarajućim tabelama.

Zadatak 6.11: Napisati C/SQL program koji redom omogućava naredne funkcionalnosti:

  1. Student započinje prijavljivanje na praksu navođenjem broja indeksa, nakon čega mu se nudi spisak numerisanih naziva predmeta za koje se može prijaviti da radi praksu. Za prijavljivanje studentu se nude samo predmeti koje je položio sa ocenom većom od prosečne ocene sa položenih ispita iz tog predmeta.
  2. Zatim student (potencijalno više puta) upisuje identifikator predmeta iz koga želi da radi praksu, ili 0 za kraj. Posle svakog izabranog predmeta studentu se nudi izmenjen spisak predmeta u kome se ne nalaze već izabrani predmeti. Odabrani predmet se unosi u tabelu PRAKSA.
  3. Na kraju se ispisuje izveštaj koji za svaki prijavljeni predmet sadrži: naziv predmeta, broj do tada prijavljenih studenata za praksu iz tog predmeta; prosečnu prolaznost (ukupnu) u poslednjih 5 godina. Izveštaj urediti u opadajućem redosledu po prolaznosti. Prolaznost računati kao količnik broja položenih ispita sa ocenom većom od 5 nevezano od statusa prijave i broja izlazaka na ispit (broja unosa u tabelu ispit sa statusom prijave različitim od ‘n’).

Svako prijavljivanje predstavlja nezavisnu transakciju. Napisati program tako da radi ispravno u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.

Potrebno je u bazi vstud kreirati naredbu koja pravi tabelu PRAKSA koja ima kolone:

Definisati primarni ključ u tabeli PRAKSA i strane ključeve nad tabelama DOSIJE i PREDMET. U ovoj tabeli se čuvaju podaci o prijavljenim praksama. Jedan student može potencijalno prijaviti više različitih predmeta, a i isti predmet može prijaviti više različitih studenata.


  1. Obratiti pažnju da se ne radi o WITH klauzi za kreiranje privremenih tabela u SELECT naredbi, već da se ova WITH klauza nekad naziva i isolation-clause