Az SQL alapú adatbáziskezelô felületek

 

 

Az elõzõekben megismert SQL nyelv egyik fõ jellemzõje, hogy hiányoznak belõle a procedúrális elemek. Ennek következtében nem lehet pusztán SQL utasításokra építve komplett alkalmazásokat készíteni, hiszen az SQL nem tartalmaz elágazási, ciklus vezérlési vagy éppen terminál felület mûködését leíró nyelvi elemeket. Így az SQL nem tekinthetõ egy alkalmazás fejlesztõ nyelvnek. Az SQL kizárólagos célja az adatbázissal történõ adatforgalom biztosítása. Ebbõl az is következik, hogy az alkalmazások készítéséhez egy más jellegû procedurális nyelvre lesz szükségünk. A piacon számos ilyen fejlesztõ nyelv áll rendelkezésre. Többek között megemlíthetjük a C, Pascal vagy Fortran nyelveket, mint a legelterjedtebb általános válú programfejlesztõ rendszereket.

 

E nyelvekben igen rugalmas eszközkészlet áll rendelkezésre a különbözõ vezérlési és IO mûveletetek ellátására, viszont nem tartalmaznak semmilyen lehetõséget, hogy a programból az adatbázisban tárolt adatokat elérhessük. Az SQL éppen e funkciók elvégézére szolgál, de ott meg a proceduráis elemeket kell nélkülöznünk. Egy adatbázis kezelést megvalósító alkalmazásnál viszont mindkét elemre szükségünk lenne. Így felmerül a kérdés, hogyan lehetne olyan fejlesztõ körneyezetet létrehozni, amelyek mindkét elemet tartalmazzák, lehetõleg úgy, hogy könnyen illeszthetõ legyen az eddigi rendszerekhez, s viszonylag kevés ráfordítással meg lehessen tanulni.

 

A felvetett problémára többféle megoldás is létezik, mint ahogy azt a piacon megjelenõ termékek is mutatják. Az egyik megoldás, hogy egy teljesen új, független nyelvet is hozzunk létre. Ennek számos hátránya van éppen a szabványosság és a megtanulhatáság vonatkozásában, viszont elõnyös lehet a hatékonyság szempontjából, hiszen levethetõk a korábbi korlátok, megkötöttségek. Emiatt célszerûnek látszik az SQL alapok megõrzése és a megismert programozási nyelvek meghagyása is. A választás ebben az esetben már arra irányul, hogy melyik irányból közelítsünk a másik komponens felé. Összefoglalóan tehát az alábbi utak állnak rendelkezésre:

 

- új fejlesztõ rendszer kidolgozása

- az SQL nyelv kibõvítése ki procedrúrális elemekkel

- a programozási nyelveket bõvítjük ki SQL elemekkel

 

A piacon megtalálható mindhárom megközelítés megvalósítása szinte minden RDBMS rendszernél. Mi a továbbiakban az Oracle rendszerén keresztül mutatjuk be az egyes változatok megvalósításait, alkalmazását.

 

Elsõként, e fejezet keretében a programozási nyelvek kibõvítésének lehetõségeit vesszük át. A fejezet tanulmányozásánál feltesszük, hogy az eddigi tanulmányaik során már találkoztak a C procedúrális nyelvvel, igy erre a nyelvre vonatkozóan fogjuk tárgyalni e terület ismeretanyagát. Mivel a fejezet fõ célja nem a C nyelv speciális rutinjainak ismertetése, hanem az adatbázisoknak e nyelvekbõl történõ elérésének bemutatása, így az itt megadott ismeretek viszonylag könnyen általánosíthatók más programozási nyelv estére is.

 

A programozási nyelvek kiegészítésénél a magas szintû nyelvek procedurális elemei közé beilleszthetjük az adatbázis kezelésére szolgáló SQL utasításokat. A kiválasztott procedurális nyelvet gazdanyelvnek szokás nevezni. A legleterjedtebb gazdanyelvek közé tartozik többek között a C, Ada, Pascal, COBOL, FORTRAN is, így szinte bármilyen programozói elôképzettséggel készíthetünk beépített SQL utasításokat tartalmazó adatbázis kezelô alkalmazásokat.

 

Az SQL alapû adatkezelõ funkciók bevonására kétféle formalizmus terjedt el a gyakorlatban. Az elsõ változat esetén a gazdanyelv utasításai közé beszúrjuk az már ismert SQL utasításokat, méghozzá olyan szintaktikával, mint ahogy azt az SQL szabvány elõírja. Így az alkalmazás forrásprogramjában a gazdanyelvi és az SQL kifejezések együttesen, egymást váltogatva fordulnak elõ. E kétféle utasításelemek szemmel láthatóan, jól elkülönülnek egymástól, hiszen lényeges különbség van egy C beli kifejezés, mint pl.

 

c = (x > 6) ? 3 : 2;

 

és egy SQL kifejezés, mint pl. a

 

INSERT INTO auto VALUES (3,’fdg345’);

 

szintaktikája között. Az ezen az elven működő kibővítéseket nevezik az SQL gazdanyelvi beágyazásának, ami arra utal, hogy a gazdanyelv utasításai közé beültetjük, beillesztjük az SQL utasításokat.

 

A másikfajta megközelítés közelebb áll a hagyományos 3GL programozási nyelvekhez, mivel itt a gazdanyelv szintaktikájának megfelelő formalizmussal lehet az SQL utasításokat végrehajtatni. Ekkor a gazdanyelv utasításai közé eljárások, függvények formájában szúrjuk be az adatkezelő tevékenységeket. Az előző INSERT utasítás például a következő függvényhívással valósítható meg az Orcale OCI rendszerében:

 

osql3(&cursor, "INSERT INTO auto VALUES (3,’fdg345’);”, -1);

 

Ebben a formalizmusban az SQL utasítások szervesen beilleszkednek a C nyelv utasításai, kifejezései közé, s csak a függvények elnevezési, a paraméterek értékei utalnak arra, hogy itt egy adatbázis kapcsolat kerül megvalósításra. Ez a fajta mechanizmus a CLI (Call Library Interface) elnevezést kapta, utalva arra, hogy itt a kapcsolat könyvtári függvények hívásán keresztül valósul meg.

 

A kétféle megközelítés ugyan formálisan igen eltér egymástól, de funkcionálisan igen közeliek, hiszen mindkettõ ugyanazt a célt szolgálja. Az egyenértékûséget mutatja, hogy mindkét formalizmussal végrehajthatók az SQL-hez tartozó utasítások teljes készlete, tehát mindkettõt lehet nyugodtan választani egy adott feladat megoldására. A kétféle megközelítés azonban a formai különbségek mellett bizonyos megközelítési, szemléletbeli eltérés is tapasztalható.

 

A beágyazott SQL elõnye, hogy

- SQL szabványokhoz közelebb álló formalizmus

- az egyszerûbb adatkezelési környezet könnyebben megvalósítható

míg a CLI a beágyazott SQL-lel szemben

- részletesebb elõkészítést igényel

- bonyolultabb formalizmussal rendelkezik

 

A CLI elõnye viszont, hogy

- természetesebb módon tudja kezeleni az összetettebb, dinamikus adatkezelési funkciókat is

- a felhasználó könnyebben kézben tudja tartani a végrehajtást

- közvetlenebebb végrehajtás

- jobban kapcsolható más rendszerekhez

 

Általános irányelvként az mondható, hogy az egyszerûbb adatkezelési funkciókat megvalósító alkalmazások esetében célszerû a beágyazott SQL bevonása, míg az összetetettebb, dinamikus rendszereknél a CLI lesz a megelelõ eszköz.

 

 

Beágyazott SQL utasítások használata

 

A beágyazott SQL utasítások fejlesztésnek menete ugyanúgy kezdõdik, mint egy hagyományos gazdanyelvi program készítése. Elsô lépcsôfokként egy szövegszerkesztôvel elôállítjuk a beépített SQL utasításokat tartalmazó gazdanyelvi forrásállományt. Ebben a gazdanyelv és az SQL utasítások együttesen szereõpelhetnek.

Eztkövetôen egy elôfordító segítségével a beépített SQL utasítások átkonvertálódnak a gazdanyelvi szintaktikának megfelelô eljáráshívásokká, ugyanis a forrásállományban a megszokott SQL szintaktikát használhatjuk, ami lényegesen eltér a programozási nyelvekben megszokott formáktól. E konverzió eredménye egy szabályos gazdanyelvi forrásszöveg lesz.

Ezután elôbb a gazdanyelvi fordító, compiler átalakítja a forrásszöveget gépi kódú object állománnyá, majd a szerkesztô, a linker összegyûjti a hivatkozott szimbólumok kódjait. Ehhez természetesen felhasználja az SQL eljárásokat tartalmazó tárgykönyvtárakat, library-kat is. A fenti lépések eredményeként elôáll egy végrehajtható, futtatható program, melyet a többi futtatható programhoz hasonlóan indíthatunk pl. a nevének a megadásával vagy éppen a RUN parancs segítségével.

A fejlesztés általános menetét mutatja be a következõ ábra:

 

 

Programfejlesztés menete

 

A beépített SQL legnagyobb elônye, hogy egyesíti a hatékony adatbáziskezelô nyelvet a hatékony algoritmusleíró, felhasználói kezelôfelület készítô programozási nyelvekkel. Mivel mindkettô viszonylag független életet élhet, nincsen összekötve a fejlôdésük, így a két nyelv függetlenül és gyorsabban is fejlôdhet, s az egyik komponens fejlesztésénél elért eredmények közvetlenül felhasználhatók a beépített SQL nyelvet tartalmazó programokba is. Mivel az SQL több gazdanyelvbe is beépíthetô, ezért lehetôség van a feladathoz és a fejlesztôgárdához igazított programozási nyelv kiválasztására, sokkal rugalmasabbá téve a fejlesztést, mintha csak egyetlen egy fejlesztôeszköz állna rendelkezésre. Az adatbázis-kezelési, rugalmassági elônyök mellett azonban azt is észre kell venni, hogy az SQL utasítások eljárásainak beépítésével természetszerûleg megnô a programok mérete, s egy adatbáziskezelô utasítás végrehajtása is több idôt vesz igénybe, mint egy normál állománykezelô utasítás elvégzése.

 

Egy beágyazott SQL-t tartalmazó programnak az adatbázissal történõ adatforgalom megvalósítása miatt tartalmaznia kell bizonyos tevékenységi elemeket. E tevékenységi elemek kitérnek az adatkapcsolat felépítésére, az adatforgalom megadására és végrehajtására és a kapcsolat lebontására. Mivel e tevékenységeket a fenti sorrendben kell végrehajtani, ezért a beágyazott SQL programokban egyfajta szekvenciát kell megvalósítani az adatkezeléshez kapcsolódó utasításoknál, mint ahogy azt a következõ ábra is mutatja.

 

 

Adatkezelõ utasítások vezérlési menete

 

A beépített SQL nyelv használatával kapcsolatban három fô kérdés merülhet fel.

Egyrészt, hogyan lehet az SQL utasításokat beépíteni a gazdanyelvi programba. Másrészt az is látható, hogy az alkalmazás azért fordul az adatbáziskezelôhöz, hogy onnan adatokat kérjen le vagy éppen adatokat helyezzen le oda. Tehát igen lényeges kérdés, hogy hogyan történik az adatcsere a gazdanyelvi program és az adatbáziskezelô között. Harmadsorban azt is tapasztaltuk már, hogy utasításainkat bizonyos esetekben nem tudta végrehajtani az RDBMS, tehát a programnak is értesülnie kell a kiadott utasítás végrehajtásának eredményességérôl, azaz meg kell oldani a hibakezelés kérdését is. A továbbiakban ezen kérdésekre összpontosítva mutatjuk be a beépített SQL használatát, mely során az Oracle Pro*C fejlesztôeszközét használjuk majd szemléltetésként, tehát gazdanyelvként a C nyelv fog szerepelni.

 

A gazdanyelvbe beépített SQL utasításokat nagyon egyszerû megkülönböztetni a gazdanyelvi parancsoktól, ugyanis minden beépített SQL utasítás az

 

EXEC SQL

 

kulcsszóval kezdôdik. Ha pl. az auto táblázatban az összes ár értéket megnöveljük 12 százalékkal a C gazdanyelvi programban két értékadás között, akkor a következôképpen néz ki a megfelelô forrásszövegrészlet:

 

a = 3;

EXEC SQL UPDATE auto SET ar = ar * 1.12;

c = f++;

 

A forrásszövegben is több soron keresztül folytatódhat egy SQL utasítás, a utasítás végét ugyanis a pontosvesszô ,s nem a sorvég karakter jelzi. A gazdanyelve beépített SQL utasítások azonban bizonyos mértékben különböznek az eddig megismert SQL utasításoktól, vannak ugyanis olyan utasítások, melyeket a korábbiakban még nem említettünk, habár az SQL szabvány tartalmazza ôket. Ezen utasításokra ugyanis csak itt, a beépített SQL esetén van szükség, s csak itt használhatók. Ezen utasítások a korábban említett problémák, mint a hibakezelés vagy adatcsere, kezelésére szolgálnak majd. A most következô utasítások tehát ismét az SQL92 részei, s csak jellegük miatt tárgyaljuk ôket az interaktív SQL szabványutasításoktól elkülönülten.

 

Az adatcsere kérdését érintve elôször, vegyünk egy olyan példát, amikor a FIAT126 típusú autó árát módosítani kell, ahol az új értéket nem tudjuk még a forrásszöveg megírásakor, a program fejlesztésekor, hanem csak a program futása során fog majd kiderülni. Azaz a felhasználó által a futás során megadott értéket kell bevinni az adatbázisba. Ez a tevékenység a beépített SQL használata esetén két lépésben hajtódhat végre. Elsôként a gazdanyelvi eszközöket felhasználva be kell kérni egy számértéket a felhasználótól, ahol az érték egy gazdanyelvi változóba fog letárolásra kerülni. A második lépésben kiadjuk az UPDATE utasítást, melyben hivatkozunk az értéket tároló gazdanyelvi változóra. Mivel ezen adatcserére szolgáló változókat a többi normál gazdanyelvi változóktól eltérôen kell kezelni az elôfordítás, és a futás során, ezért a beépített SQL nyelv megköveteli, hogy az adatcserére szolgáló gazdanyelvi változókat az elôfordító számára is ismertté tegyük, úgymond deklaráljuk ôket. A változók deklarálása egy deklarációs blokkban történik, melynek kezdetét a

 

BEGIN DECLARE SECTION

 

SQL utasítás és a lezárását, a végét az

 

END DECLARE SECTION

SQL utasítás jelzi. Mivel ezek is SQL utasítások, ugyanúgy az EXEC SQL kulcsszóval kell bevezetni ôket, mint a már ismert SELECT vagy UPDATE utasításokat. A deklarációs blokkban minden olyan gazdanyelvi változónak szerepelni kell, melyeket egy SQL utasításban adatcserére használunk fel, ahol az adatcsere jelenthet adat fogadását és elküldését is. A szokásos terminológiában input gazdanyelvi változónak nevezik azon változókat, melyek értékeit bevisszük az adatbázisba, és output gazdanyelvi változónak nevezik azon változókat, melyek az adatbázisból kapnak értéket. A deklarációs blokkban meg kell adni a változó nevét és adattípusát is. Az itt deklarált változókat nem szabad a blokkon kívül még egyszer deklarálni. A blokkban megadott változódeklarációknak, adattípusoknak követniük kell a gazdanyelv szabályait. Azonban csak olyan adattípusok használhatók, melyek a kompatibilisek a hivatkozott adatbázismezô típusával. Egyes adattípusok használata azonban sohasem megengedett, így pl. a Pro*C esetében a struct adattípus nem adható meg a deklarációs blokkon belül.

Példaként vegyük azt az esetet, amikor az auto táblázat típus és ar mezôjének lekérdezésre, módosítására hozunk létre két gazdanyelvi változót:

 

EXEC SQL BEGIN DECLARE SECTION;

int auar; /* autoár */

char tipus[26];

EXEC SQL END DECLARE SECTION;

 

Az így megadott auar változót felhasználhatjuk az ar mezô módosítására. Mint látható nem kell megegyezni a gazdanyelvi változó és a hivatkozott mezô nevének egymással. A szabályok azonban nem is tiltják, hogy a változó neve megegyezzen egy mezônévvel és ebben esetben a név alapján már viszont nem tudnánk eldönteni, hogy az SQL utasításban szereplô név a mezôt vagy a gazdanyelvi változót jelöli-e. A kétértelmûségek elkerülése végett a beépített SQL megkívánja, hogy az SQL utasításokban felhasznált gazdanyelvi változók azonosítói elé egy kettôspontot tegyünk. A példaként felvetett feladat ezek alapján következôképpen oldható meg:

 

scanf ("%d", &auar);

EXEC SQL UPDATE auto SET ar = :auar WHERE tip LIKE 'FIAT126%';

 

Ha a gazdanyelvi változót nem input céllal, hanem output célra kívánjuk használni, akkor a lekérdezô, azaz az SELECT utasításhoz kell kapcsolni. Mivel az eddig megismert SELECT utasítás nem definiálja, hogy hova kerüljön az eredmény, viszont a beépített SQL esetén szükség van a fogadó változó kijelölésére, ezért az SQL szabvány egy módosított SELECT utasítást is tartalmaz erre a célra. Ez a kibôvített SELECT utasítás tartalmaz egy új opciót, melyet az INTO kulcsszó vezet be, melynek segítségével kijelölhetô, hogy az eredmény mely változókba kerüljön le. Az INTO kulcsszó közvetlenül a FROM kulcsszó elôtt foglal helyet. Ha egyszerre több mezô értékét kérdezzük le, akkor több gazdanyelvi változónak is kell szerepelni az INTO opcióban, egymástól vesszôvel elválasztva, s a mezôértékek a megadott sorrend alapján kerülnek át a gazdanyelvi változókba. Az INTO opció használata során ügyelni kell a típusok kompatibilitására is. A legnagyobb és legkisebb autoar és kiírása a következô utasítással lehetséges, feltéve hogy deklaráltuk mind az auar1 mind az auar2 gazdanyelvi változókat a deklarációs blokkban:

 

EXEC SQL SELECT MAX(ar), MIN(ar) INTO :auar1, :auar2 FROM auto;

printf ("Max auto ar: %d\n", auar1);

printf ("Min auto ar: %d\n", auar2);

 

A SELECT többi része megegyezik a korábban megismert SELECT utasítással, azaz tartalmazhat pl. ORDER BY, GROUP BY, HAVING, join mûvelet opciókat is.

A SELECT utasítás esetén azonban mindenképpen felmerül egy fontos probléma is. A SELECT utasítás ugyanis egy eredménytáblázatot szolgáltat, mely rendszerint nemcsak egyetlen egy rekordból áll. Ha például az autók típusait kívánjuk kiíratni, akkor több nevet is fog tartalmazni az eredménytáblázat. Ebben az esetben az INTO kapcsoló után nem állhat egyetlen egy skalár változó, mert abba nem helyezhetô el az egész táblázat. Ugyan megoldásként kínálkozik hogy tömböt adjunk meg az INTO kulcsszó után, azonban ennek a megoldásnak is van hátránya. Ugyanis a tömböket rögzített méretûre kell deklarálni a gazdanyelvi programban, így elôre kellene ismerni a SELECT által szolgáltatott eredménytáblázat méretét, hogy annál nagyobb méretûre válasszuk a tömböt.

Ez a megoldás azonban több szempontból is kifogásolható. Egyrészt nem lehet mindig elôre megbecsülni az eredménytáblázat méretét, így nagy valószínûséggel rossz tömbméretet fogunk megadni. Másrészrôl az sem szerencsés, hogy mindegyik eredményrekordnak helyet foglalunk, hiszen sok alkalmazásnál nincs szükségünk mindegyik rekordra egyidejûleg, hanem mindig csak egyet íratunk ki belôle. Ekkor felesleges mindnek helyet foglalni a memóriában, elég lenne egyenként átnézni ôket.

A beépített SQL a több rekordoból álló eredménytáblák rekordjainak biztonságos lekérdezésére fejlesztette ki a kurzor, cursor szerkezetet.

 

A kurzor olyan adatstruktúra, mellyel egy több rekordot tartalmazó eredménytáblázat rekordjai sorban egymásután beolvashatók. A használata több lépésben történik. Elsôként a kurzor deklarálását végezzük el, mely során ismertté tesszük a szerkezet. Ennek során kell megadni azt is, hogy mely SELECT utasítás eredménytáblázatának a lekérdezésére fog szolgálni a kurzor. A második lépés a kurzor megnyitása, amikor is a megadott SELECT végrehajtódik, azaz létrejön az eredménytáblázatot tartalmazó struktúra, s ezt követôen egy belsô mutató rááll az eredménytáblázat elsô rekordjára. A harmadik fázisban a rekordok lekérdezése történik. Az SQL89 értelmezésében, és a legtöbb létezô RDBSM estében a belsô mutató egyesével halad elôre. Minden rekordolvasás automatikusan eggyel elôre lépteti a mutatót. Visszafelé azonban nem mozgatható a mutató, tehát csak szekvenciálisan dolgozhatjuk fel a rekordokat.

Az SQL92 szabvány azonban már megengedi a mutató tetszôleges elôre vagy hátra irányú mozgatását is. Az utolsó lépés a kurzor lezárása, amikor felszabadul az eredménytáblázatnak lefoglalt hely, megszûnik a mutató is. A kurzorhoz tartozó SELECT utasítás nem tartalmazhatja az INTO opciót hiszen ennek elkerülésére készült.

 

Az SQL89 esetében ha menetközben szeretnénk a mutatót az ez elsô eredményrelációra visszaállítani, akkor azt csak a kurzor lezárásával és újbóli megnyitásával tehetjük meg. Ekkor azonban már nem biztos hogy ugyanazt az eredménytáblázatot kapjuk vissza, hiszen minden megnyitás a lekérdezés végrehajtását is jelenti, s ugyanazon SELECT parancs két egymást követô végrehajtásnál más és más eredményt szolgáltathat, hiszen közben megváltozhatott az adatbázis is. A kurzor kezeléséhez a következô utasítások kapcsolódnak:

 

kurzor definiálása:

 

DECLARE kurzornév CURSOR FOR SELECT ...;

 

kurzor megnyitása:

 

OPEN kurzornév;

 

kurzor lekérdezés, eredményrekord beolvasás:

 

FETCH [NEXT|PREV|FIRST|LAST] kurzornév INTO változólista;

 

kurzor lezárás:

 

CLOSE kurzornév;

 

 

Egy FETCH utasítás egy rekordot olvas be az eredménytáblázatból és hatására az aktuális eredményrekord mezôinek értékei sorba elhelyezôdnek az INTO kulcsszó után megadott gazdanyelvi változókba. A változólista több gazdanyelvi változó azonosítót tartalmazhat, egymástól vesszõvel elválasztva. Az elsô mezô értéke az elsô változóba, a második mezô értéke a második változóba kerül, stb. Itt is ügyelni kell a típusok konvertibilitására.

Az utasításban megadott irányjel kulcsszavak jelentése a következô:

 

NEXT mozgás elôre eggyel

PREV mozgás hátra eggyel

FIRST mozgás az elsô rekordra

LAST mozgás az utolsó rekordra

 

Az elmondottakból következik, hogy az eredménytáblázat lekérdezésére több FETCH utasítást kell kiadni, mégpedig annyit ahány rekordot az eredménytáblázat tartalmaz. A programban mindez egy ciklus szervezését igényli. A ciklus leállási feltételének ellenôrzésére pedig szükséges az, hogy tudjuk, mikor olvastuk be már az összes rekordot. Erre a beépített SQL a hibakezelô lehetôségeit használja fel: hibajelzés generálódik, ha a FETCH utasítás elérte az eredménytáblázat végét. Igy a programban figyelni kell a hiba jelentkezését, s ha fellépne, ki kell lépni a beolvasó ciklusból.

 

Ezzel át is léptünk a harmadik nagy területre, a hibalekezelés területére. A gazdanyelvi programok alapesetben nem értesülnek az RDBMS mûveletek során fellépô hibákról, így a végrehajtott SQL utasítás sikerességétôl vagy sikertelenségétôl függetlenül fut tovább a gazdanyelvi a program. Mivel normál esetben egészen más utasításokat kell végrehajtani, ha az SQL utasítás lefutott vagy ha nem futott le, ezért a programozónak expliciten be kell építenie a gazdanyelvi programba az SQL hibák kezelésére vonatkozó elemeket. A hibakezelés alapvetôen két módon végezhetô el a beépített SQL programok esetén, lehet ugyanis

 

- közvetlen vagy

- közvetett

 

a hibakezelés módja. A közvetlen hibakezelés esetén az alkalmazásban deklarálni kell egy megadott szerkezetû struktúrát, amelyhez az RDBMS is hozzáférhet. Ez a struktúra arra szolgál, hogy az RDBMS elhelyezze benne a legutoljára elvégzett SQL utasításhoz tartozó hibakódot, azaz hogy ezen keresztül jelezze az alkalmazással, sikerült-e végrehajtani a parancsot vagy sem, s ha nem akkor mi volt a hiba oka. Az alkalmazásban az

 

INCLUDE sqlca;

 

SQL utasítással lehet a hibakezelést szolgáló struktúrát deklarálni. Az SQLCA egy kommunikációs területként is felfogható, igaz igen egyoldalú kommunikáció folyik ezen a téren az alkalmazás és az RDBMS között, ugyanis az RDBMS csak információ küldésre használja ezt a szerkezetet. Az SQLCA struktúra több mezôbôl épül fel, melyek közül az alábbi mezôk tekinthetôk a legfontosabbaknak:

 

- sqlcode : az kiadott utasítás végrehajtása során felmerült hiba kódszáma, a mezô 0 értéket tartalmaz, ha sikeres volt a mûvelet

- sqlerrm : a felmerült hiba szöveges megadása, ez valójában egy összetett mezô, mely két almezôt tartalmaz:

- sqlerrml : a hibaüzenet hossza

- sqlerrmc : a hibaüzenet szövege

Látható, hogy a szöveg hosszát nem egy lezáró karakter jelzi, hanem egy külön változó tartalmazza.

- sqlerrd : egy hatelemû egésztípusú tömb a státuszkódok jelzésére, az egyik státuszkód pl. a mûvelet során feldolgozott rekordok darabszámát tartalmazza (azaz pl. mennyi rekordot módosított az UPDATE utasítás)

 

A közvetlen hibakezelésnél az alkalmazás az SQLCA struktúra ellenôrzésével, olvasásával szerez tudomást a felmerült hibákról. Tehát az alkalmazás feladatai közé tartozik az SQLCA rendszeres ellenôrzése és az esetlegesen fellépô hibák lekezelésének, elhárításának a megoldása. A SQLCA struktúrát az alkalmazásban is gazdanyelvi struktúraként kell kezelni, így pl. a hiba észlelése és a hibaüzenet kiírása az alábbi C nyelvi utasításokkal lehetséges:

 

if (sqlca.sqlcode) {

sqlca.sqlerrm.sqlerrmc[sqlca.sqlerrm.sqlerrl] = '\0';

printf ("Hiba: %s\n", sqlca.sqlerrm.sqlerrmc);

...

}

 

A példa második C utasítása elhelyezi a hibaüzenet végére a C sztringeket lezáró null karaktert.

 

A közvetett hibakezelés esetén az alkalmazásnak nem kell direkt módon lekérdezni az SQLCA mezôinek értéket, egy automatikus lekérdezési opciót lehet beállítani, amely automatikusan és folytonosan ellenôrzi az SQLCA mezôinek értékeit, és a megadott hiba esetén elvégez egy megadott tevékenységet.

A közvetett hibakezelés esetén is ki kell adni az

 

INCLUDE sqlca;

 

utasítást, az automatikus hibakövetés elindításának SQL parancsa:

 

WHENEVER hiba tevékenység;

 

A parancsban az alábbi hibatípusok figyelését jelölhetjük ki:

 

- SQLERROR: hiba lépett fel a végrehajtás során, az SQL utasítás nem hajtódott végre

- SQLWARNING: az SQL utasítás végrehajtódott, de figyelmeztetô üzenetet küldött az RDBMS, ugyanis valamilyen nem várt esemény következett be.

- NOT FOUND: az RDBMS nem talált a feltételnek megfelelô rekordot. Ez hiba két esetben következhet be. Egyrészt ha a SELECT utasítás üres eredménytáblázatot adna vissza, másrészt akkor ha a FETCH utasítás elérkezett a lista végére, s nem tud további rekordot visszaadni.

 

A parancsban a tevékenység helyére az alábbi kulcsszavak adhatók meg:

 

- CONTINUE: a program folytatása

- STOP: a program leállítása

- GOTO cimke: az vezérlés átadása az adott cimkére

- DO fuggv(): az adott függvény meghívása

 

A WHENEVER utasítás igen kényelmes és egyszerûen használható eszközt ad a kezünkbe az SQL hibák kezelésére, azonban érdemes a használata során néhány momentumra, jellegzetességre odafigyelnünk. Az elsô észrevétel, hogy a WHENEVER utasítás hatásköre nem a gazdanyelvi program blokkjaihoz kötött, hanem az utasításnak a forrásszövegbeli pozíciója dönti el, hogy mely SQL utasításokra vonatkozik. Ezt úgy képzelhetjük el, hogy az elôfordító köti hibakezelést az SQL utasításokhoz, s ahogy halad elôre a forrásszövegben, mindig az utoljára megtalált WHENEVER utasítást tekinti érvényesnek az aktuális SQL utasításra vonatkozólag. Így egy elôl elhelyezett WHENEVER utasítás ha, másik WHENEVER utasítást nem adunk meg, egészen a forrásszöveg végéig érvényben marad. Ez egyedül a GOTO tevékenység esetében igényel nagyobb odafigyelést, ugyanis ekkor a megadott címke rendszerint csak egy gazdanyelvi programegységben, pl. egy függvényben látható, s a többi függvényben, ami még a forrásszövegben utána található, a WHENEVER utasításban szereplô hivatkozások már ismeretlen címkére fognak mutatni, ami a fordítás során fog jelentkezni hibaként.

A másik észrevétel a WHENEVER utasítással kapcsolatban, hogy ha figyelmetlenek vagyunk, könnyen végtelen ciklust idézhetünk elô vele. Ha ugyanis egy WHENEVER utasítással egy megadott címkére küldöm, ahol pl. visszagörgetem a tranzakciót, és az elôzô WHENEVER itt is érvényben marad, akkor egy olyan komolyabb hiba esetén, amikor már a tranzakció-lezárás is hibához vezet, végtelen ciklust kapunk. A vezérlés ugyanis egy örökös ciklusban oda-vissza ugrik a címke és a tranzakció-lezárás között, mivel a WHENEVER a címkére küldi a vezérlést a tranzakció-lezárástól, a címkétôl pedig a tranzakció-lezáráshoz vezet az út a megadott programkódban.

 

A hibakezeléshez kapcsolódóan meg kell még említeni az indikátor változók fogalmát is. Az indikátor változók segítségével tulajdonképpen azt ellenôrizhetjük, hogy helyes adatokat kaptunk-e vissza a gazdanyelvi változóinkba. Ha ugyanis egy mezô értéke a lekérdezett rekordban üres, vagyis NULL érték van benne, akkor a gazdanyelvi változóban megtalálható érték hamis eredményt fog mutatni, hiszen a hagyományos programozási nyelveknek nincs eszközük a NULL érték kezelésére. Ezért a beépített SQL bevezette az indikátorváltózók használatát, melyek típusukra nézve rövid egészként deklarálhatók. Az indikátorváltozókat a deklarációs blokkban kell deklarálni, és az SQL utasításokban közvetlenül a kapcsolódó gazdanyelvi változó után szerepel, szintén kettôsponttal bevezetve. Egy indikátorváltozó egy SQL utasításban egy gazdanyelvi változóhoz köthetô. Ugyanaz az indikátorváltozó az egymás utáni különbözô SQL utasításokban más-más gazdanyelvi változóhoz kapcsolható. Ha egy output gazdanyelvi változóhoz kötjük, a lekérdezés után az alábbi értékek szerepelhetnek az indikátorváltozóban:

 

-a nulla (0) érték azt jelzi, hogy helyes adat van a kapcsolódó

gazdanyelvi változóban

-a mínusz egy (-1) érték azt jelzi, hogy az adatbázismezô NULL

értéket tárolt, s a gazdanyelvi változóban helytelen adat van

-a pozitív érték azt jelzi, hogy a mezôben tárolt érték nem fért

el a gazdanyelvi változóban, így ott egy csonkított értéket

találunk, az indikátorváltozó ilyenkor a mezôben tárolt adat

eredeti hosszát adja meg

 

Az indikátorváltozót az input gazdanyelvi változók esetében is használhatjuk, s ilyenkor a NULL érték bevitelének egyik eszköze lehet. Ha ugyanis a -1 értéket rakjuk az indikátorváltozóba, akkor a kapcsolódó gazdanyelvi változóból a NULL érték kerül át az adatbázismezôbe.

 

A hibakezelés fôbb vonásainak áttekintése után egy pillanatra megint visszakanyarodunk a kurzor szerkezethez. Az elôzôekben most már látható, hogy az eredménytáblázat rekordjainak lekérdezési ciklusának ellenôrzésére vagy az SQLCA közvetlen lekérdezése vagy a WHENEVER utasítás használata szolgáltat megoldást. A könnyebben kezelhetô WHENEVER lehetôséget kiválasztva a ciklus megkezdése elôtt egy

 

WHENEVER NOT FOUND GOTO kilepes;

 

SQL utasítással a vezérlés a ciklus utáni, 'kilepes' címkével ellátott sorra adható át, ha a FETCH utasítás elérte az eredménytáblázat végét.

 

Az kurzor segítségével tehát sikerült az SQL halmazorientált szemlélete a hagyományos programozási nyelvek rekordorientált szemlélete közötti konverziót illetve hidat megvalósítani. A lekérdezett rekord adatai a FETCH révén átkerülnek a gazdanyelvi változókba, s ezután már a program szabadon gazdálkodhat a kiolvasott értékekkel. A FETCH azonban nem teszi lehetôvé, hogy az aktuális rekord adatait módosítsuk, így úgy tûnik, hogy továbbra is csak a halmazorientált UPDATE utasítás marad számunkra.

Van azonban egy közvetlenebb megoldás erre a problémára is a beépített SQL esetén, mely rekordok egyenkénti, rekordorientált módosítását vagy törlését is lehetôvé teszi bizonyos értelemszerû korlátozások között. Ennek elsô lépcsôjeként a kurzor deklarációjánál meg kell adnunk, hogy a kiválasztott rekordokat módosítani vagy éppen törölni kívánjuk. Ezt a kurzor deklarációjának a végén megadott

 

FOR UPDATE [ OF (m1 [,m2...,mi])]

 

opcióval tehetjük meg. Ha a kifejezésben mezôlistát is megadunk, akkor csak az adott mezôk lesznek módosíthatók. Ha nem adunk meg mezôlistát, akkor minden mezô módosítható és a rekord törölhetô is. A rekordorientált módosítás második lépésében a rekordfeldolgozó cikluson belül kiadott UPDATE vagy DELETE utasítást úgy szûkíthetjük le az éppen feldolgozott rekordra, hogy az SQL utasítások WHERE szelekciós feltételében a

 

CURRENT OF kurzornév

 

feltételt adjuk meg. Ekkor a mûveletet csak a megadott kurzor aktuális rekordjára fog vonatkozni. Az elôbb azt is említettük még, hogy nem minden esetben módosíthatók a kurzor rekordjai. Vannak ugyanis olyan esetek, amikor a kurzor SELECT utasítás eredménytáblázata igen összetett módon származik le a bázistáblázatokból, amikor is nem egyértelmû, hogy mely helyen kell módosítani vagy éppen törölni. Az RDBMS-ek rendszerint csak az egy táblázatra vonatkozó SELECT utasítások esetében engedik meg a módosítást, feltéve hogy azok nem tartalmaznak csoportképzési vagy DISTINCT opciókat.

 

A kurzor szerkezetének bemutatására egy komplettebb példát adunk a most következôkben. A feladatunk az, hogy a FIAT autók árait 15%-kal, a LADA autók árait 12%-kal növeljük meg.

 

EXEC SQL DECLARE autokurz CURSOR FOR

SELECT tip, ar FROM auto WHERE tip LIKE 'FIAT%' OR

tip LIKE 'LADA%' FOR UPDATE OF ar;

EXEC SQL OPEN autokurz;

 

EXEC SQL WHENVER NOT FOUND GOTO vege;

 

while (1) {

EXEC SQL FETCH autokurz INTO :atip,:aar:aai;

if (aai < 0) continue;

if (tip[0] == 'F') {

EXEC SQL UPDATE auto SET ar = ar*1.15 WHERE CURRENT OF autokurz;

} else {

EXEC SQL UPDATE auto SET ar = ar*1.12 WHERE CURRENT OF autokurz;

}

}

vege:

EXEC SQL CLOSE autokurz;

 

A folyamat minden lépése már ismerôs számunkra, elôbb deklaráljuk a kurzort, melyben a FIAT és LADA típusú rekordokból a típust és árat kérdezzük le. Mivel módosítani is kívánjuk a rekordokat megadjuk az FOR UPDATE opciót. Ezután megnyitjuk a kurzort, azaz végrehajtódik a lekérdezés. A lekérdezô ciklust a WHENEVER utasítással készítjük elô, mely megadja, hogyha elérnénk az eredmény táblázat végét, a vezérlés kerüljön a vege címkével jelzett sorra. A ciklusban beolvassuk az típus és ar mezô értékeit a megfelelô gazdanyelvi változókba. Az ar mezôhöz egy aai azonosítójú indikátorváltozót is tettünk, hogy ellenôrizhessük, van-e már ára az autónak vagy még nincs. Ha nincs továbblépünk a következô rekordra. Ha van akkor a típustól függôen módosítjuk az aktuális rekordot.

 

A SQL utasításokban eddig tudatosan csupán skalár értékeket használtunk, a gazdanyelvi tömbváltozók ugyanis csak igen korlátozottan használhatók az SQL utasításokban. A legtöbb RDBMS csak egydimenziós gazdanyelvi tömbök deklarálását engedélyezi. A tömbök használata különben nagyon hasonlít a skalár változók használatához. A tömböket megadhatjuk output és input változóként is. Output változóként használva azonban ügyelni kell arra, hogy az eredménytáblázat beleférjen a tömbbe, különben hibajelzést kapunk. Pl a

 

EXEC SQL BEGIN DECLARE SECTION

int tar [100];

EXEC SQL END DECLARE SECTION

 

EXEC SQL SELECT ar INTO :tar FROM auto;

 

utasítások esetén, ha a SELECT száznál több rekordot adna vissza, SQL hiba lépne fel, ha pedig kevesebb a rekordok száma, akkor normálisan lefut az utasítás, s az elsô rekord bekerül a tömb elsô elemébe, a második bekerül a második tömbelembe stb. Sokkal kényelmesebb viszont a tömböknek mint input változóknak a használata. Ekkor ugyanis az

 

EXEC SQL BEGIN DECLARE SECTION

int tar [100];

char nev[100][20];

EXEC SQL END DECLARE SECTION

 

EXEC SQL INSERT INTO tabla VALUES (:tar, :nev);

 

utasítás hatására mind a 100 rekord bekerül a tabla táblázatba. A kiadott INSERT utasítás ugyanis sorba veszi a tömbelemeket és mindegyikre végrehajtja a kijelölt utasításokat. Igy az utasítás hatása megegyezik a következô gondolati ciklussal:

 

for (i=0; i<100; i++) {

EXEC SQL INSERT INTO tabla VALUES (:tar[i], :nev[i]);

}

 

Az elôbb azért említettünk gondolati ciklust, mert a megadott ciklus nem felel meg a beépített SQL szabályainak, mivel nem lehet benne tömbelemekre hivatkozni. A tömböket az INSERT utasításhoz hasonlóan használhatjuk a DELETE vagy UPDATE utasításokban is.

 

Az eddigiekben ismertetett utasítások áttekintésére egy egyszerû példát veszünk, melyben az auto táblázat sorait kérdezzük le. Mint már korábban említettük az Oracle esetén minden felhasználónak azonosítania kell önmagát mielôtt az adatbázishoz hozzáférhetne, s ez az alkalmazások esetén is így van. Ezért a gazdanyelvi programunk is tartalmaz egy bejelentkezési utasítást, melyben megadunk egy azonosító nevet és a egy hozzátartozó jelszót. Sikeres bejelentkezés esetén férhetünk csak hozzá a megadott adatbázis objektumokhoz.

A bejelentkezés utasítása:

 

CONNECT :nev IDENTIFIED BY :jelszo;

 

A bejelentkezés sajátossága, hogy a nevet és a jelszót csak input változókon keresztül lehet megadni, szövegkonstans nem szerepelhet benne.

A példában még két, eddig nem tárgyalt utasítás is szerepel, melyek az RDBMS tranzakció szervezését vezérlik. Mint már említettük, a tranzakció egy logikailg összefüggô mûveletsor, mely egy egységes egészként elfogadtatható vagy elvethetô. Ez utóbbi esetben minden eddig benne elvégzett mûvelet meg nem történté válik. A tranzakció elfogadásának utasítása a

 

COMMIT

 

elvetésének parancsa pedig a

 

ROLLBACK

 

kulcsszó. Nézzük ezután a példát, melyben egy új, a Pro*C-ben használható adattípust is megismerhetünk. Ez VARCHAR adattípus változó hosszúságú szövegek kezelésére alkalmas, s tulajdonképpen egy struktúrát takar, melynek két mezôje van, egyik a szöveget tartalmazza, másik az aktuális hosszat jelzi. A deklarációban megadott hossz a maximális szöveghosszat jelenti.

 

 

#include <stdio.h>

/* output es input valtozok deklarlasa */

EXEC SQL BEGIN DECLARE SECTION;

int auar; /* ar mezo */

char tipus[30]; /* típus mezo */

char rsz[6]; /* rendszam */

short iar; /* indikator */

VARCHAR nev[40]; /* felhasznalo azonosito neve */

VARCHAR jelszo[40]; /* felhasznalo jelszava */

EXEC SQL END DECLARE SECTION;

/* hibakezelesi kommunikacios terulet */

EXEC SQL INCLUDE sqlca;

/* foprogram */

main()

{

/* hibakelezes definialasa */

EXEC SQL WHENEVER SQLERROR DO hiba();

 

/* bejelentkezes a rendszerbe */

strcpy (nev.arr,"SCOTT");

nev.len = strlen (nev.arr);

strcpy (jelszo.arr,"TIGER");

jelszo.len = strlen (jelszo.arr);

EXEC SQL CONNECT :nev IDENTIFIED BY :jelszo;

 

/* lekerdezesi ciklus deklaracioja */

EXEC SQL DECLARE kurz CURSOR FOR

SELECT rsz, ar FROM auto WHERE tip = :tipus;

 

/* tipus lekerdezes */

printf ("tipus = ");

scanf ("%s", tipus);

/* lekerdezes elinditasa */

EXEC SQL OPEN kurz;

 

/* lekerdezo ciklus */

EXEC SQL WHENVER NOT FOUND GOTO veg;

 

while (1) {

EXEC SQL FETCH kurz INTO :rsz, :auar:iar;

if (iar == 0) {

printf ("rendszam=%s ar=%d\n", rsz, auar);

}

}

 

/* kilepes a ciklusbol */

veg:

/* kurzor lezaras */

EXEC SQL CLOSE kurz;

/* tranzakcio lezaras */

EXEC SQL COMMIT WORK RELEASE;

exit(0);

}

 

/* hibakezelo rutin */

hiba()

{ /* vegtelen ciklus elkerulese */

EXEC SQL WHENEVER SQLERROR CONTINUE;

/* hibauzenet kiirasa */

printf ("hiba: %s\n", sqlca.sqlerrm.sqlerrmc);

/* tranzakcio visszagorgtes */

EXECSQL ROLLBACK WORK RELEASE;

exit(1);

}

 

A beépített SQL utasításoknál a tartalmi különbségek ellenére bizonyos formai azonosságokat is felfedezhetünk. Így az utasításokat a kiválasztott szempont szerinti viselkedésük alapján különbözô csoportokba válogathatjuk szét. Az elkövetkezôkben kétféle szempontot vizsgálunk meg, melybôl az elsô azt emeli ki, hogy az SQL utasítás kinek is szól. Ha ugyanis végigtekintünk az eddig vett utasításokon és kiemelünk belôle két utasítást, mondjuk az UPDATE és a WHENEVER utasításokat, akkor láthatjuk, hogy az UPDATE hatására az RDBMS fog bizonyos mûveletsort elvégezni, tehát úgy is mondhatjuk, hogy az UPDATE az RDBMS-nek szólt. Ezzel szemben a WHENEVER utasítás nem fog semmilyen RDBMS tevékenységet kiváltani, szerepe az, hogy az elôfordítóval közölje, milyen hibákra figyelje, és hogyan kezelje le az észlelt hibákat. Így a WHENEVER az elôfordítónak szóló utasítást jelent.

Ha végigtekintünk a többi utasításokon is, akkor megállapíthatjuk, hogy az összes többi utasítás is e két csoport valamelyikébe sorolható be. Az egyik csoportba tartoznak azok az utasítások, melyek az RDBMS-nek szólnak, azaz melyek RDBMS mûveletsort eredményeznek. Ezen utasításokat végrehajtható utasításoknak nevezik. E csoportba tartozik többek között a

SELECT

INSERT

UPDATE

DELETE

is. A másik csoport elemei az elôfordítónak szóló utasításokat foglalja magába, melyeket összefoglalóan deklarációs utasításoknak szokta nevezni. Ezen csoport tagjai a

 

BEGIN DECLARE SECTION

END DECLARE SECTION

DECLARE

WHENEVER

INCLUDE

 

utasítások.

 

Az említett szempont szerint képzett csoportok között ugyan némi különbséget találhatunk az utasítások kiadása, használata között, de igazán lényeges, formai eltérést nem tapasztalhatunk. Ezzel szemben a következôként vizsgált szempont szerinti osztályozásnál már jelentôsebb eltéréseket kapunk a kialakult csoportok között.

A második szempontot röviden úgy fogalmazhatnánk, hogy az az SQL utasítások dinamikusságát vizsgálja. Részletsebben ez alatt a következôket értjük. A gazdanyelvi program tartalmazhat olyan SQL utasításokat, melyek minden eleme már a forrásszöveg megírásakor ismert. Egy ilyen utasítás lehet pl. a következô:

 

 

EXEC SQL UPDATE auto SET ar = ar *1.34 WHERE tip LIKE ‘FIAT%';

 

Ennél az utasításnál már a forrásszöveg írásakor tudjuk, hogy milyen utasítást, mûveletet kell végrehajtani, tudjuk milyen objektumokkal és értékekkel kell elvégezni a mûveletet, azaz az elvégzendô utasítás minden részletét ismerjük a forrásszöveg megírásakor. Az ilyen jellegû utasításokat statikus utasításoknak nevezzük.

 

Vettünk azonban már olyan utasításokat is a korábbiakban, melyekben bizonyos adatértékeket nem ismertünk a forrásszöveg megírásakor, csak a futás során derült ki az értékük. Ezt a feladatot a gazdanyelvi változók használatával oldhattuk meg. A következô utasítás erre ad példát:

 

EXEC SQL UPDATE auto SET ar=ar*:nov WHERE tip LIKE 'FIAT%';

 

Ebben az esetben továbbra is ismert, hogy milyen utasításokat kell kiadni, ismertek az utasításokban szereplô objektumok és az utasítás szerkezete is adott, csupán a benne szereplô egyes adatértékek ismeretlenek a forrásszöveg megírásakor. Az ilyen jellegû utasításokat kvázi-statikus utasításoknak nevezhetjük, hiszen használata nagyban hasonlít a statikus utasítások használatához.

 

Az eddigiekben ugyan nem találkozhattunk olyan megoldásokkal, habár a beépített SQL majd ezt is megengedi, hogy az utasítás szerkezete is ismeretlen a forrásszöveg megírásakor, s csupán a futás során dôl el, hogy végül is milyen szerkezetû, milyen jellegû SQL utasítást kell végrehajtani. A következôkben az ilyen, dinamikusnak nevezett SQL parancsok használatát ismertetjük. Természetesen csak a végrehajtható utasításokat adhatjuk meg ilyen módon, illetve még ezek között is vannak megszorítások, így a

 

OPEN

FETCH

PREPARE

CLOSE

EXECUTE

 

parancsok nem használhatók dinamikus módon. A dinamikus SQL segítségével jelentôsen megnövelhetô az alkalmazás rugalmassága, hiszen minden futás alkalmával más és más utasítás hajtható végre. E rugalmasság ára azonban a bonyolultabb programozási technikában és a valamivel lassúbb végrehajtásban fizetendô meg. A dinamikus SQL-t ezért akkor célszerû használni, amikor feltétlenül szükség van rá, azaz akkor ha nem ismerjük a programírás során, hogy

- milyen utasítást kell végrehajtani

- milyen feltételeket kell figyelembe venni

- mely adatbázis-objektumokon kell a mûveletet végrehajtani.

 

Mivel az utasítások szövege a futás során, dinamikusan jön létre, ezért, a futás alatt kell elvégezni egy sor olyan elôkészítô tevékenységet, melyek normál körülmények között már az elôfordítás és a fordítás során lefutnak. A dinamikus SQL parancsok használatának általános lépései az alábbiakban foglalhatók össze:

 

- egy szöveges változóba letároljuk a végrehajtandó SQL utasítás szövegét

- az utasítás szövegét elküldjük az RDBMS-hez ellenôrzés végett (parsing). Az ellenôrzés során az RDBMS megvizsgálja, hogy szintaktikailag helyes-e az utasítás, illetve megnézi, hogy a hivatkozott objektumok léteznek-e és elérhetôk-e.

- a következô lépésben a felhasznált gazdanyelvi változókat jelöljük ki (binding). A létrehozott utasítássorban ugyanis nem a felhasznált gazdanyelvi változók azonosító nevei szerepelnek (a változók azonossága lényegtelen az utasítás szintaktikai,jogosultsági ellenôrzése során), hanem csak úgynevezett helyettesítôszimbólumokat (placeholder) tartalmaznak. Ezen lépés feladata az egyes helyettesítô szimbólumokhoz a kiválasztott gazdanyelvi változók hozzárendelése. E megoldás révén ugyanaz a parancssor több különbözô változó helyettesítéssel is végrehajtható egymásután.

- Az utolsó fázisban az így összeállított és ellenôrzött utasítás tényleges végrehajtása történik meg, azaz az utasítást átadjuk az RDBMS-nek végrehajtásra.

 

Az egyes lépések bonyolultságát tekintve a dinamikus SQL utasításokat az

alábbi csoportokba sorolhatjuk be:

 

A: azon nem Query utasítások, melyekben nem szerepelnek gazdanyelvi változók

B: azon nem Query utasítások, melyekben ismert darabszámú és típusú gazdanyelvi változó szerepel

C: azon Query utasítások, melyekben ismert darabszámú és típusú gazdanyelvi változó szerepel

D: azon utasítások melyekben ismeretlen darabszámú és típusú gazdanyelvi változó szerepel

 

Az A esetben a binding fázis elmarad, s az ellenôrzés és a végrehajtás fázisai is egyetlen egy utasítással hívhatók meg. Így ebben az esetben elegendô elõször összeállítani a parancsot tartalmazó szöveget, s utána rögtön elküldhetjük végrehajtásra az

 

EXECUTE IMMADIATE szöveg;

 

utasítással, melyben a szöveg jelenthet string típusú konstanst és változót is. A szöveg szimbólum a parancsot tartalmazó szöveget jelenti. Egy egyszerû példa lehet az A típusra a következô utasítássor:

 

EXEC SQL BEGIN DECLARE SECTION;

char szoveg[40];

EXEC SQL END DECLARE SECTION;

 

strcpy (szoveg,"UPDATE auto SET ar=ar*1.34");

EXEC SQL EXECUTE IMMEDIATE szoveg;

 

Mint a fenti példából is látható, az SQL utasítást tartalmazó szövegben nem kell megadni sem az 'EXEC SQL' elôtagot, sem az utasítást lezáró ';' karaktert sem. A szoveg változó tartalmát nemcsak értékadással, hanem beolvasással is meghatározhattuk volna.

 

A B típusú dinamikus SQL parancsok esetén már három lépés szükséges a végrehajtáshoz. Elsôként elôállítjuk a parancsot tartalmazó szöveges változót, majd ellenôrizzük annak helyességét, és legvégül egy utasítással elvégezzük a gazdanyelvi változók hozzárendelését és a tulajdonképpeni végrehajtást is. A szintaktikai ellenôrzése parancsa:

 

PREPARE parnev FROM szoveg;

 

Az utasításban a szoveg az utasítást tartalmazó változó vagy konstans, s a parnev egy, a parancshoz rendelt egyedi azonosító nevet jelöl, célja, hogy a további parancsokban egyértelmûen azonosítja végrehajtandó dinamikus utasítást. A végrehajtás és a változó hozzárendelés együttes parancsa:

 

EXECUTE parnev USING valtlist;

 

ahol a valtlist a parancsban felhasználandó gazdanyelvi változók listáját jelöli, a listában vesszôvel elválasztva az egyes gazdanyelvi változókat, melyben minden változóhoz köthetô egy indikátorváltozó is. Mind a gazdanyelvi változók, mind az indikátorváltozók neveit kettôspont elôzi meg. Példaként tekintsük az elôzô feladat azon módosítását, amikor az ár mezô változásának mértékét egy input változóval adjuk meg:

 

EXEC SQL BEGIN DECLARE SECTION;

char szoveg[40];

float arany;

EXEC SQL END DECLARE SECTION;

 

strcpy (szoveg,"UPDATE auto SET ar=ar*:arany");

EXEC SQL PREPARE parancs FROM :szoveg;

printf ("arany:");

scanf ("%f", &arany);

EXEC SQL EXECUTE szoveg USING :arany;

 

Ebben az esetben a parancsszöveg már tartalmaz helyfoglaló szimbólumokat a késôbbi input változók számára. A helyfoglaló szimbólum neve lehet tetszôleges, független a késôbb hozzákötött input változó nevétôl. A USING opcióban annyi input változót kell megadni, ahány helyfoglaló szimbólum található a parancsszövegben. Az összerendelés az elôfordulási sorrend alapján történik.

 

Ha egy lekérdezést, azaz egy SELECT utasítást szeretnénk használni dinamikus módon, akkor vagy a C vagy a D módszer áll rendelkezésünkre. A dinamikus SELECT utasítás egyik jellegzetessége, hogy csak a kurzor szerkezet segítségével hajtható végre. A lekérdezésnél, ha a program írásakor már ismerjük az eredménytáblázat mezôinek és lekérdezés input változóinak a darabszámát és típusát, akkor a következô lépéseket kell végrehajtanunk egymásután:

 

- a SELECT utasítás szövegét lehelyezzük egy szöveges változóba

- ellenôriztetjük az utasítás helyességét az elôbb már megismert

 

PREPARE parancs FROM szoveg;

 

utasítással

- deklarálunk egy kurzort a lekérdezéshez:

 

DECLARE kurnev CURSOR FOR parancs;

 

amelyben most nem egy SELECT utasítást kell megadni a FOR után részben, hanem a létrehozott és ellenôrzött parancs azonosítóját.

- hozzárendeljük az input változókat a helyfoglaló szimbólumokhoz és egyúttal végre is hajtjuk a lekérdezést, elôállítva az igényelt eredménytáblázatot. A tevékenység parancsa:

 

OPEN kurnev USING valtlist;

 

A hozzárendelendô változók listájának megadása megegyezik az elôbb említett EXECUTE parancsban használt USING opcióval.

- lekérdezzük az eredménytáblázat rekordjait egymásután a már ismert

 

FETCH kurnev INTO valtlist;

 

utasítással, ahol a valtlist most az output változók listáját tartalmazza.

- lezárjuk a kurzort a

 

CLOSE kurzor;

 

utasítással.

 

A C típusú dinamikus SQL parancsok használatának bemutatására visszanyúlunk a korábban ismertetett mintaprogramhoz, úgy módosítva azt, hogy a lekérdezés valamilyen, elôre nem ismert feltételt tartalmaz a típusra nézve.

 

 

#include <stdio.h>

/* output es input valtozok deklarlasa */

EXEC SQL BEGIN DECLARE SECTION;

int auar; /* ar mezo */

char tipus[30]; /* típus mezo */

char rsz[6]; /* rendszam */

short iar; /* indikator */

VARCHAR nev[40]; /* felhasznalo azonosito neve */

VARCHAR jelszo[40]; /* felhasznalo jelszava */

VARCHAR szoveg[50]; /* parancsszoveg */

EXEC SQL END DECLARE SECTION;

/* hibakezelesi kommunikacios terulet */

EXEC SQL INCLUDE sqlca;

/* foprogram */

main()

{

char feltetel[20];

/* hibakelezes definialasa */

EXEC SQL WHENEVER SQLERROR DO hiba();

 

/* bejelentkezes a rendszerbe */

strcpy (nev.arr,"SCOTT");

nev.len = strlen (nev.arr);

strcpy (jelszo.arr,"TIGER");

jelszo.len = strlen (jelszo.arr);

EXEC SQL CONNECT :nev IDENTIFIED BY :jelszo;

 

/* parancssor eloallitasa */

printf ("kerem az ar mezore vonatkozo feltetelt:");

scanf ("%s", feltetel);

/* rr az ar mezo helyfoglalo szimboluma */

strcpy (szoveg.arr,"SELECT rsz, ar FROM auto WHERE :rr ");

strcat (szoveg.arr, feltetel);

szoveg.len = strlen (szoveg.arr);

 

/* parancs ellenorzese */

EXECSQL PREPARE parancs FROM :szoveg;

 

/* lekerdezesi ciklus deklaracioja */

EXEC SQL DECLARE kurz CURSOR FOR parancs;

 

/* lekerdezes elinditasa */

EXEC SQL OPEN kurz USING :ar;

 

/* lekerdezo ciklus */

EXEC SQL WHENVER NOT FOUND GOTO veg;

 

while (1) {

EXEC SQL FETCH kurz INTO :rsz, :auar:iar;

if (iar == 0) {

printf ("rendszam=%s ar=%d\n", rsz, auar);

}

}

 

/* kilepes a ciklusbol */

veg:

/* kurzor lezaras */

EXEC SQL CLOSE kurz;

/* tranzakcio lezaras */

EXEC SQL COMMIT WORK RELEASE;

exit(0);

}

 

/* hibakezelo rutin */

hiba()

{ /* vegtelen ciklus elkerulese */

EXEC SQL WHENEVER SQLERROR CONTINUE;

/* hibauzenet kiirasa */

printf ("hiba: %s\n", sqlca.sqlerrm.sqlerrmc);

/* tranzakcio visszagorgtes */

EXECSQL ROLLBACK WORK RELEASE;

exit(1);

}

 

A C típusú dinamikus parancsokhoz rendelt megkötöttségek, mint látható azért kellettek, hogy a forrásszövegben meg tudjuk adni mind az input mind az output változók listáját. Ha viszont nem ismertek az input változók vagy a helyfoglaló szimbólumok, akkor a megadott formátumú OPEN és FETCH utasítások nem használhatóak, s ekkor jutunk el a D típusú dinamikus SQL parancstípusokhoz.

 

A D esetben külön kell közölni az RDBMS-sel a végrehajtás elôtt, hogy mennyi és milyen változók szerepelnek a kiadott utasításban. Ehhez a rendszer egy leíró struktúrát (sqlda) bocsát rendelkezésre. E struktúrák feltöltésében nyújtanak segítséget a DESCRIBE utasítások. Az sqlda egyes mezôit viszont csak közvetlenül lehet feltölteni, s ezen utasítások megvalósítása rendszerfüggô és igen összetett feladat is, ezért ennek részletes bemutatását most mellôzzük. A végrehajtandó tevékenységek alapvonalaiban megegyeznek az elôbb említettekkel, azzal a különbséggel, hogy az

 

EXECUTE parancs USING valtlist;

OPEN kurzor USING valtlist;

FETCH kurzor INTO valtlist;

 

utasításoknál a változólista helyett mindenütt a létrehozott és feltöltött descriptor struktúrákat kell megadni, így az alábbi utasításokat kell használni:

 

EXECUTE parancs USING DESCRIPTOR leiro;

OPEN kurzor USING DESCRIPTOR leiro;

FETCH kurzor USING DESCRIPTOR leiro;

 

A rendszerben két különbözô típusú descriptor létezik, az egyik az input változók, a másik az output változók leírására alkalmas. Maga a descriptorok feltöltése igen hosszú folyamat és sok specifikus ismeretet igényel, ezért nem részletezzük mélyebben.

 

 

A CLI program interface

 

Mint már korábban is említettük, az SQL alapú adatbáziskezelést megvalósító alkalmazások egyik lehetséges csoportját az CLI alapú alkalmazások alkotják, melynek fő jellemzője, hogy a gazdanyelvi programba a gazdanyelv szabályait teljesítő formalizmussal RDBMS-t kezelő függvényhívásokat illeszthetünk be. A ProC mechanizmustól eltérően itt nem szabvány SQL utasítások formájában hajtódik végre az adatkezelés, hanem a gazdanyelv függvényhívásian alapszik. Természtesen az RDBMS felé kiadott relációs algebrán alapuló utasítások továbbra is SQL utasítások lesznek, csak más formalizmusban, más előkészítési lépéseken keresztül hajtódnak végre.

A CLI lehetőségek tárgyaláásra az Oracle rendszer OCI, azaz Oracle Call Interface rendszerét fogjuk bemutatni.

 

Az OCI formalizmus egy alacsonyabb szinten valósítja meg az adatkapcsolatot, mint a beágyazott SQL programok, hiszen itt a felhasználó felelõsége és feladata az adatkezelõ utasítások elõkészítése, a gazdanyelvi változók hozzákötése az adatkezelõ utasításokhoz. Az OCI szemléletmódja sokkal közelebb áll a hagyományos programozói szemlélethez, hiszen nem tartalmaz a gazdanyelvtõl idegen kifejezéseket. A meghívandó rutinok a gazdanyelv normál rendszer rutinjaihoz hasonlóan egy könyvtárban foglalnak helyet. A program lineklése során e könyvtárból kerülnek át a végrehajtási kódok a futtatandó állományba. Az OCI programok esetében nincs tehát szükség egy elõfordítási fázisra, hiszen a felhasználó rögtön az adtkezelõ rutinokat adja meg a forrásszövegében. Az OCI alapú programfejelesztés lépésit mutatja be a következõ ábra.

 

 

 

Az RDBMS rendszerek rendszerint több 3GL gazdanyelvhez is tartalmaznak CLI könyvtárat, melyekbõl most mi itt is a C nyelvhez kapcsolódó OCI könyvtárra fogunk támaszkodni az ismertetésnél.

 

Az C OCI rendszer minmtegy 25 függvényt tartalmaz az adatkezelõ SQL utasítások kiadására. Hasonlóan a ProC rendszerhez, az egyes adatkezelõ tevékenységek egy viszonylag kötöttebb sorrendben követik egymást. Ez a kötöttség itt mégjobban érezhetõ, hiszen sokkal részletesebben kell elõkészíteni az egyes SQL utasításokat. A részletesség pozitív oldala viszot, hogy így egy általánosabb végrehajtási mechanizmust kapunk, amely viszonylag könnyen kiterjeszthetõ a dinamiikus utasítások területére is.

 

Az OCI programok adatkezelõ utasításainak a végrehajtási kapcsolatát mutatja be az alábbi, az OCI kézikönyv alapján elkészített ábra.

 

 

 

Az ábra alapján látható, hogy az SQL kiadása elõtt sokkal többelõkészítõ lépést kell tenni: létre kell hozni egy bejelentkezési adatterületet, melyen a kapcsolat fõbb jellemzõi kerülnek letárolásra. Eztkövetõen egy cursor struktúrát kell létrehozni, amely egy SQL utasítás végrehajtására szolgál. Egy cursor szerkezet egyidejûleg csak egyetlen egy SQL utasítást foglalhat magába, de egyszerre több cursor is nyitva lehet. A cursor táblába fel kell vinni az SQL utasítás szövegeét, amit az RDBMS-sel ellenõriztetni kell. Az elemzés során elõáll az utasítás optimális végrehajtási terve is. Az utasítás hivatkozhat az adatbázis adatok mellett gazdanyelvi változókra is, melyek az SQL utasításokban adatbázis idegen elemenként, hivatkozásokként jelennek meg.

 

A program elején, az adatforgalom megkezdése elõtt be kell jelentkezni az Oracle-hez a megadott név és jelszó segítségével. A sikeres bejelentkezés után létrejön a kapcsolat az Oracle adatbázissal. A bejelentkezés is egy függvényhíváson keresztül valósítható meg. A bejelentkezés függvénye az

 

int olon ( struct ldadef *lda, char *user_id, int usr_id_len, char *passwd,

int passwd_len, int audit_flag);

 

Az olon függvény elsõ argumentuma egy struktúra, mely LDA (logon data area), azaz bejelentkezési adatterületként ismert. E struktúra mintegy közvetítõ szerepet tölt be a program és az RDBMS között, melynek fõ funkciója a bejelentkezésnél elõforduló esetleges hiba okának felderítése. A bejelentkezés sikerességének ellenõrzésére egy egyszerû és jól kezelhetõ módszert kínál az OCI. Az olon függvény ugyanis zérus értékkel tér vissza, ha sikeres volt a végrehajtás, s nemzérus érték pedig hiba felléptére utal. A sikeresség ellenõrzésének ez a módja azonban nemcsak az olon függvényre hanem az összes többi OCI függvényre is vonatkozik. Az LDA struktúra mezõit mutatja be a struktúra C nyelvbeli definiciója:

 

struct ldadef {

short lda_v2_rc; /* V2 return kód, már nem használt */

unsigned char fill1[10]; /* nem használt */

short lda_rc; /* visszatérési kód */

unsigned char fill2[19]; /* nem hasnált */

unsigned int lda_ose; /* OS függõ hibakód */

unsigned char lda_chk; /* check byte */

unsigned char lda_sysparam[26]; /* rendszer paraméterek */

};

 

Az olon függvény második paramétere a bejelentkezõ Oracle felhasználó azonosítására szolgál. Itt egy sztringet kell megadni, melynek tartalmaznia kell a felhasználó nevét. A név mellett a jelszó is megadható a

 

név/jelszó

 

alakban, s ebben az esetben nincs szükség a passwd jelsó mezõ kitöltésére sem. Ha az átadott sztringek végét a C-ben szokásos '\0' karakter zárja le, akkor a sztringek hosszát jelzõ usr_id_len és passwd_len mezõk üresen maradhatnak. A utólsó paraméter egy naplózásjelzõ.

 

A következõ példa egy tipikus bejelentkezési programrészletet mutat, melyben a felhasználó neve scott és a jelszava tiger.

 

struct ldadef {

short lda_v2_rc; /* V2 return kód, már nem használt */

unsigned char fill1[10]; /* nem használt */

short lda_rc; /* visszatérési kód */

unsigned char fill2[19]; /* nem hasnált */

unsigned int lda_ose; /* OS függõ hibakód */

unsigned char lda_chk; /* check byte */

unsigned char lda_sysparam[26]; /* rendszer paraméterek */

};

struct ldadef lda;

char uid[20];

 

....

 

strcpy (uid, "scott/tiger");

if (olon(&lda, uid, -1, (char *) -1, -1, 0)) {

printf ("hiba a bejelentkezésnél\n");

exit(-1);

}

else {

printf ("sikeres bejelentkezés\n");

}

 

A sikeres bejelentkezés után SQL parancsokon keresztül lehet az adatbázisban tárolt adatokhoz hozzáférni. Az SQL utasítások végrehajtásához azonban definiálni kell a programban egy CDA (cursor data area), azaz egy kurzor adatterültet, mely a program és az Oracle rendszer közötti kapcsolattartásra szolgál az SQL parancs végrehajtásához kapcsolódóan. E struktúrán keresztül lehet a fellépõ hibákról és a parancs végrehajtásának állapotáról értesülni. A CDA terület C-beli definíciója:

 

struct crsdef {

short crs_v2_rc; /* v2 hibakód, nem használt */

short crs_sql_fc; /* SQL mûvelet kódja */

unsigned long crs_rpc; /* feldolgozott rekordok darabszáma */

short crs_peo; /* szintaktikai hiba helye az SQl parancson belül */

unsigned char crs_oci_fc; /* meghívott OCI függvény kódja */

unsigned char crs_fil; /* nem használt */

unsigned short crs_rc; /* hibakód */

unsigned char crs_wrn_flg1; /* figyelmeztetõ jelzések jelzõje*/

unsigned char crs_wrn_flg2; /* figyelmeztetõ jelzések jelzõje */

unsigend int crs_cn; /* *kurozr azonosító/

unsigned char crs_rowid[13]; /* utóljára érintett rekord poziciója */

unsigned int crs_ose; /* OS hibakód */

unsigned char crs_chk; /* check byte*/

unsigned char crs_fill[26]; /* foglalt terület */

};

 

A programban nem elég egy crsdef tipusú változót létrehozni, azt hozzá is kell kötni egy aktív bejeléentkezéshez, avgyis meg kell nyitni a kurzor területet. A megnyitást végzõ függvény szintaktikája:

 

int oopen (struct crsdef *cursor, struct ldadef *lda, char *dbn,

int dbn_len, int area_size,char *user_id,int user_id+_len);

 

A megnyitásnál az lesõ paraméter a kurzor terület címét, a második pedig a bejelentkezési adatterületet adja meg. A többi paraméter értéke szokásos setben elhagyható. A következõ példa a kurzor létrehozását mutatja általános esetre vonatkoztatva.

 

struct crsdef {

short crs_v2_rc; /* v2 hibakód, nem használt */

short crs_sql_fc; /* SQL mûvelet kódja */

unsigned long crs_rpc; /* feldolgozott rekordok darabszáma */

short crs_peo; /* szintaktikai hiba helye az SQl parancson belül */

unsigned char crs_oci_fc; /* meghívott OCI függvény kódja */

unsigned char crs_fil; /* nem használt */

unsigned short crs_rc; /* hibakód */

unsigned char crs_wrn_flg1; /* figyelmeztetõ jelzések jelzõje*/

unsigned char crs_wrn_flg2; /* figyelmeztetõ jelzések jelzõje */

unsigend int crs_cn; /* *kurozr azonosító/

unsigned char crs_rowid[13]; /* utóljára érintett rekord poziciója */

unsigned int crs_ose; /* OS hibakód */

unsigned char crs_chk; /* check byte*/

unsigned char crs_fill[26]; /* foglalt terület */

};

struct crsdef cursor;

if (oopen (&cursor, &lda, (char *) -1, -1, -1, (char *) -1, -1) ) {

hiba();

}

else {

sikeres();

}

 

A CDA terület létrehozása után elindítható az SQL utasítás végrehajtása. Ennek elsõ lépéseként az SQL utasítást el kell küldeni szintaktikai ellenõrzésre, s végrehajtási terv elkészítésére. Az elõkészítést végrehajtó függvény az osql3 függvény, melynek paraméterezése:

 

int osql3 (struct crsdef *cursor, char *sql_stmt, int sql_len);

 

A függvény elsõ paramétere egy létrehozott CDA területre mutat. A második paraméter a végrehajtandó SQL utasítás szövegét tartalmazza. A harmadik paramérre csak akkor van szükség, ha az SQl szöveg végét nem a '\0' karakter határolja.

 

A következõ programrészlet egy példát mutat be adatbvitelre vonatkozó SQL utasítás esetén:

 

strcut crsdef cursor;

struct ldadef lda;

...

olon(&lda, uid, -1, (char *) -1, -1, 0))

oopen (&cursor, &lda, (char *) -1, -1, -1, (char *) -1, -1);

osql3 (&cursor, "INSERT INTO auto VALUES (3,4)", -1);

 

Azon SQL utasításokban, melyek nem vonatkozhatnak gazdanyelvi változókra, azaz a DDL és DCL utasítások esetén az osql3 függvényhívás nemcsak elõkészíti, hanem végre is hajtja a megadott SQL parancssort. A DML és Query utasítások esetén azonban még további lépésekre van szükség a végrehajtáshoz.

 

Ennek oka, hogy az alkalmazások rendszerint több egymástkövetõ adatkezelõ utasítást is kiadnak ugynarra a táblára vonatkozóan. Mivel az egyes módosítási vagy új értékek nem ismertek a program megírásakor, mivel azok csak a program futása során határozódnak meg, ezért a program forrásszövegében sem lehet elõre megadni az aktuális értékeket. Így a DML utasításokban az értékeket nem konstansokként adjuk meg, hanem egy programváltozót jelölünk ki, amelynek aktuális értéke fogja meghatározni a bevivendõ értéket. Így ugyanazzal az SQL utasítással több különbözõ értéket lehet felvinni vagy módosítani a parancs egymástkövetõ meghívásaival.

 

Ha a DML vagy query utasítás egy programváltozót tartalmaz, amelynek aktuális értéke kerül felhasználásra a végrehajtás során, akkor az OCI mechanizmusban közölni kell az Oracle RDBMS-sel, hogy pontosan mely és milyen tipusú gazdanyelvi változókhoz kötödnek a SQL utasításban szereplõ változó hivatkozások. Vagyis az OCI esetében a SQL utasítás szövegében nem szükségszerû hogy létezõ gazdanyelvi változóra hivatkozzunk, mivel egy külön lépés keretében lehet a szereplõ hivtkozásokat és a gazdanyelvi változókat összerendelni. Az SQL utasításokban szereplõ, változókat reprezentáló szimbólumokat helyfoglaló szimbólumoknak, azaz placeholder-eknek nevezik. E szimbólumok lehetnek azonosító névvel és azonosító számmal is megadva. Ez utóbbira ad példát a következõ query utasítás:

 

SELECT nev, kor FROM dolgozok WHERE oszt := :1 AND fiz > :2;

 

A példában két szimbólum szerepel, az 1 és 2, melyek értékei a végrehajtáskor határozódnak meg. Természetesen a végrehajtáshoz 1 és 2 szimbólumoknak létezõ gazdanyelvi váltosókhoz kell kötõdniük.

 

A helyfoglaló szimbólumok és a gazdanyelvi változók egymáshoz kapcsolódását a

 

int obndrn (struct crsdef *cursor, int sqlvarnum, void *progvar,

int progvl, int ftype ,int scale ,short *indp ,char *fmt ,int fmt, int fmtt);

 

illetve a

 

int obndrv (struct crsdef *cursor, char *sqlvar ,int sqlvl, void *progvar,

int progvl, int ftype ,int scale ,short *indp ,char *fmt ,int fmtl

,int fmtt);

 

függvényhívások végzik el. Az obndrn függény a számmal megadok, míg az oobndv függvény az azonosító névvel megadott helyfoglaló szimbólumok hozzárendelésre szolgál.

 

Az obndrn esetében az sqlvarnum a szimbólum számértéke, a progvar a gazdanyelvi változó címe, a progvl a változó tárolási hossza, ftype a változó tipusa, a scale nem haznált paraméter, az indp jelzi hogy NULL értéket kell-e átadni a programváltozó értéke helyett. Ha az érték negatív a NULL érték fog átkerülni,. különben a gazdanyelvi változó értéke kerül át. A további paraméterek nem hasznéltak a C nyelvben. Azz obndrv hívásnál az sqlvar a változó azonosító nevét, míg az sqlvl a név hosszát tartalmazza. Ha a neve '\0' határolja, értéke elhagyható, vagyis -1 értéket szerepelhet a helyén.

 

A következõkben vegyünk két példát a változók hozzárendelésére. Az elsõ SQL utasítás legyen

 

osql3(&cursor, "SELECT id, nev FROM dolgozo WHERE ar > :1", -1);

 

Tegyük fel, hogy a fizlim gazdanyelvi változót kívánjuk hozzákötni az 1 szimbólumhoz. Ekkor a hozzárendelést végzõ függvényhívás:

 

obndrn (&cursor, 1, &fizlim, (int) sizeof (int), INT, -1, (short *) -1,

(char *) -1, -1, -1);

 

Ha a végrehajtandó SQL utasításban a szimbólum szöveges azonosítású, pl.

 

osql3(&cursor, "SELECT id, nev FROM dolgozo WHERE ar > :xx", -1);

 

akkor a

 

obndrv (&cursor, ":xxx", -1, &fizlim, (int) sizeof (int), INT, -1,

(short *) -1, (char *) -1, -1, -1);

 

lesz az összekötést végzõ függvényhívás. A példában INT, azaz egész volt a gazdanyelvi változó tipusa. Az egész tipus mellett használhatók például még a NULSTR (szöveges), FLOAT (valós)és DATE (dátum) adattipusok is.

 

A query utasítások esetében nemcsak az input értékek kötódhetnek gazdanyelvi változókhoz, hanem a lekérdezések eredményét tároló output értékek is. Ahogy a ProcC programokban, így itt sem engedhetõ meg, hogy egy

 

SELECT * FROM auto;

 

lekérdezés eredménye mindennemû kontrollnélkül kikerüljün az alkalmazás képernyõjére. Így itt is gazdanyelvi változóókba kell átirányítani a lekérdezés eredményeit. Úgy ahogy a bemenõ szimbólumoknál, itt is hozzá kell kötni az eredmény minden mezõjét egy-egy gazdanyelvi változóhoz. A hozzákötést a

 

int odefin (struct crsdef *cursor, int position, void *buffer,

int buffl, int ftype ,int scale ,short *indp ,char *fmt ,int fmtl

,int fmtt ,short retl , short rcode);

 

Egy odefin hívás az eredménytábla egyetlen egy mezõjét fogja hozzákötni valamelyik gazdanyelvi változóhoz. A függényben az position az output szimbólum sorszáma az eredmény rekordon belül, a buffer a gazdanyelvi változó címe, a bufl a változó tárolási hossza, ftype a változó tipusa, a scale nem haznált paraméter, az indp jelzi hogy NULL érték került-e be a gazdanyelvi változóba. Ha az érték negatív a NULL érték került át,. különben a gazdanyelvi változó értéke kerül át. A retl az átadott érték hossza. A további paraméterek nem használtak a C nyelvben.

 

A következõ példa az odefin használatát mutatja be. A végrehajtandó SQL lekérdezés:

 

SELECT ar, tip FROM auto;

 

Az eredménylista értékeinek hozzárendelése gazdanyelvi változókhoz, ha az ar mezõ értékét az auar, a tip mezõ értékét pedig az autip változóban kívánjuk elhelyezni:

 

odefin (&cursor, 1, &auar, (int) sizeof (int), INT, -1, (short *) -1,

(char *) -1, -1, -1, &ret[0], &ret_codes[0]);

odefin (&cursor, 2, &autip, 20, NULSTR, -1, (short *) -1,

(char *) -1, -1, -1, &ret[1], &ret_codes[1]);

 

A példában azért kellett kétszer is meghívni az odefin függvényt, mivel az eredménylistában két mezõ is szerepel.

 

Az SQL lekérdezések végrehajtása elõtt tehát ismerni kell, hogy milyen adatokat fog küldeni az RDBMS válaszként. Az alkalamzások azonban bizotsítani tudnak olyan lehetõséget is, hogy a felhasználó a futás során határozza meg a végrehajtandó lekérdezést. Ebben az esetben nem lehet elõre tudni a válaszrekordok szerkezetét sem. Hogyan lehet ezt a problémát megoldani?

 

A megoldást az OCI egy ujabb függvénye adja, amely arra szolgál, hogy egy elküldött lekérdezésrõl meghatározza a válaszként adandó rekordok szerkezetét. A szerkezetet lekérdezõ függvény alakja a köbvetkezõ:

 

int odsc(struct crsdef *cursor, int position, short *dbsize, short fsize,

short *rcode, short dbtype, char *cbuf, short cbufl, short *dsize);

 

Egy odsc hívás az eredménytrekord egy mezõjét adja vissza, így a teljes válaszrekord szerkezet feltérképezéséhez többször egymásután is ki kell adni e függvényt. A függvényhívás visszatérési értéke mutatja, hogy van-e még ujabb eleme a válaszrekordnak.

Az odsc függvény paraméterei a következõ jelentéssel bírnak. A posotion a lekérdezett mezõ sorszáma a válaszrekordban, a dbsize a mezõ maximális mérete, az fsize e mazõ legutóljára beolvasott értékének a mérete, rcode a mezõ legutóljára beolvasott értékéhez tartozó return kód,. dbtype a mezõ adattipusának a kódja, cbuf a mezõ adatbázisbeli azonosító neve, cbufl a név hossza, s dsize a mezõ értékének maximális kijelzési hossza.

 

Ha mind az input mind az output szimbólumokhoz elkészült a gazdanyelvi változókhoz történõ hozzárendelés, akkor lehet elvégezni az SQL utasítás tényleges végrehajtását. A tényleges végrehajtás is egy függvényhíváson keresztül valósítható meg, melynek formátuma:

 

int oexec (struct crsdef *cursor);

 

Mint látható, hogy e függvény paraméterezése jóval egyszerûbb, mint az elõzõ függvényeknek, ugyanis a végrehajtáskor már minden paramétert beállítottunk az elõkészítõ függvények segítségével. E paraméterek letárolásra kerültek a cursor struktúrán keresztül, így elegendõ a parancs végrehajtásához a megfelelõ cursor szerkezetre hivatkozni.

Mint korábban említettük, a DDL és DCL utasítások esetében az osql3 függvény egyben a végrehajtást is magába foglalja, így az oexec csak a DML és query utasítások esetén válik szükségessé.

 

A query utasítások esetében a ProC rendszerben külön problémát jelentett, hogy a válaszként megkapott adatok több rekordból álló táblázatok is lehetnek. A probléma forrása az volt, hogy a gazdanyelvi változók nem alkalmasak ismeretlen méretû rekordhalmaz közvetlen feldolgozására, hiszen szemléletük a rekordorientált megközelítésen alapul, azaz egyidejûleg egy rekord feldolgozására készültek fel. A ProC rendszerben a kurzor struktúra alkalmazásával tudtuk az RDBMS relációorientált és a gazdanyelvi program rekordorientált adatkezelését összekapcsolni. Mivel az adatkezelés ezen megközelítésbeli különbsége az OCI alkalmazások esetében is fennáll, így itt is szükség lesz egy hasonló megoldásra a több rekordból álló eredménytáblák feldolgozásához.

 

Az OCI rendszerekben azonban nincs különbség az elõkészítés tekintetében az egy rekordot, illetve a több rekordot eredményezõ lekérdezések között. Mindkét esetben ugyanazon lépések sorozatán keresztül kell eljutni az oexec függvényhívásig. Az eredmény elérése tekintetében is csupán abban van különbség a kétféle lekérdezések között, hogy hányszor kell elvégezni a rekordbeolvasást az eredménytáblából. Az OCI tehát minden lekérdezést úgy kezel, mint több eredményrekodot visszaadó lekérdezés. Így az egy rekordot szármztató query egy speciális esetként értelmezhetõ.

 

Az ereménytáblából az

 

int ofetch (struct crsdef *cursor);

 

függvényhívással lehet a következõ erdményrekord adatait átvinni a korábban kijelölt gazdanyelvi változókba. A output adatokat tartalmazó gazdanyelvi változókat az odefin függvénnyel rendelhettük az egyes eremény mezõkhöz. Az ofetch függvény zérus értékkel tér vissza, ha még van rekord az eredménytáblában. Mivel elõre nem tudható az eredményrekordok darabszáma, így itt is egy ciklussal oldják meg a lekérdezést. Az ofetch visszatérési értéke 1403 ha már nincs több rekord az eredménytáblában. Egyéb hiba esetén más lesz a visszatérési érték.

 

A következõ minta egy lekérdezési ciklust mutat be a C gazdanyelv felhasználásával.

osql3(&cursor, “SELECT DISTICT kor FROM DOLGOZO”, -1);

odefin (&cursor, 1, &dkor, sizeof (int), INT, -1, (short *)-1, (char *) -1,

-1, -1, (short *) -1, (short *) -1);

oecex (&cursor);

for (; ;) {

if (rv = ofetch (&cursor)) break;

printf (“kor = %d\n”, dkor);

}

 

A parancs végrehajtása után, az eredményrekordok feldolgozása után a program befejezése elõtt célszerû a kurozr terület felszabadítása. Ehhez a kurzor területet le kell zárni a

 

int oclose(strcut crsdef *cursor);

 

függvényhívásal.

 

A program végleges leállítása elõtt az adatbázissal való kapcsolatot is le kell zárni, azaz ki kell jelentkezni az adatbázisból. Az adatbázisból való kilépésre szolgáló függvény a

 

int ologof (struct ldadef *lda);

 

függvény. Meghívásával kijelentkezünk az LDA által kijelölt adatbázisból és felszabadítja az LDA által elfoglalt területet is.

 

Az elõzõekben említett függvények átölelik az OCI legfontosabb adatkezelõ funkcióit. Az OCI rendszer e függvények mellett még számos egyéb funkciót ellátó komponenssel rendelkezik, melyek magukba foglalják a tranzakciókezelés és hibakezelés tevékenységeit is. Ezek közül megemlíthetjük még az alábbi függvényeket, mint gyakrabban használt elemeket:

 

A tranzakció véglegesítétése:

int ocom(struct ldadef *lda);

 

A tranzakció visszagörgetése:

int orol(struct ldadef *lda);

 

Adott hibakódú Oracle hiba szövegének meghatározása:

int oermsg (short hibakód, char *szövegbuffer);