Dr. Vermes Mátyás1
2005. február
Az SQL2 könyvtár szabványosított adatbáziskezelő API-t nyújt CCC programok számára. A ,,szabványosított'' jelző azt jelenti, hogy ugyanaz a program ugyanazt a működést produkálja több adatbáziskezelővel is. Jelenleg az Oracle és PostgreSQL adatbáziskezelőket támogatjuk. Koncepciónk szerint az Oracle-t azoknak az ügyfeleknek szánjuk, akik nem engedhetnek meg maguknak alacsony költségvetésű projekteket, a Postgrest pedig azoknak, akik bíznak a szabad szoftverekben. Természetesen ilyen típusú interfész készíthető volna más adatbáziskezelőkhöz is (DB2, Interbase, stb.).
A jelen dokumentáció programozóknak szól, számukra leggyorsabban példaprogramokkal lehet megvilágítani a lényeget. Lássuk tehát a lehetőségeket: Az alkalmazások egyidejűleg belinkelhetik az Oracle és Postgres könyvtárat. A két könyvtár párhuzamosan ugyanolyan függvényneveket definiál, ámde különböző névterekben, ezért a nevek nem ütköznek.
con1:=sql2.oracle.sqlconnectionNew() //sql2.oracle névtér
con2:=sql2.postgres.sqlconnectionNew() //sql2.postgres névtér
Most van két adatbáziskapcsolatunk, egy Oracle és egy Postgres. Hogy az sqlconnectionNew() függvény pontosan milyen felhasználóként, hova jelentkezik be, az pillanatnyilag nem lényeges.
Tételezzük fel, hogy van egy bankszámlákat tartalmazó táblánk, aminek van egy számlaszám és egy egyenleg oszlopa. Lekérdezzük ezeket az sqlquery objektummal:
q1:=con1:sqlqueryNew("select * from szamla") // Oracle lekérdezés
...
q1:close
q2:=con2:sqlqueryNew("select * from szamla") // Postgres lekérdezés
while( q2:next )
? q2:getchar("szamlaszam"), q2:getnumber("egyenleg")
end
q2:close
Ugyanaz a lekérdezés tableentity objektummal:
t1:=szamla.tableEntityNew(con1) // Oracle tábla
...
t2:=szamla.tableEntityNew(con2) // Postgres tábla
rowset:=t2:select
while( (rowentity:=rowset:next)!=NIL )
? rowentity:szamlaszam, rowentity:egyenleg
end
Új adatrekord létrehozása tableentity objektummal:
rowentity:=t2:instance
rowentity:szamlaszam:="111111112222222233333333"
rowentity:egyenleg:=0
rowentity:insert
Módosítás tableentity objektummal:
rowentity:=t2:find("111111112222222233333333")
if( rowentity!=NIL )
rowentity:egyenleg+=100
rowentity:update
end
A példában szereplő szamla.tableEntityNew() függvény adatbázisfüggetlen, azaz ilyen objektumgyártó függvényekből bináris könyvtárat lehet létrehozni, ami minden adatbáziskezelővel működik. Az objektumgyártó függvények kódját programmal generáljuk a táblák adatbázisfüggetlen XML leírásából.
Mint látjuk az SQL2 interfész két legfontosabb eleme az sqlquery és tableentity osztályok. Az sqlquery körülbelül azt tudja, mint ami a JDBC 1.0 specifikációban van. A fejlettebb specifikációkkal kapcsolatban a J2EE Útikalauz Java Programozóknak (szerk. Nyékyné Gaizler Judit, ELTE TTK Hallgatói Alapítvány, Budapest, 2002) könyv 475. oldalán olvashatjuk:
,,JDBC 2.1 alap API: Az előző verzióhoz képest már tetszőleges sorrendben lehet feldolgozni az eredménytáblákat, melyeket már nem csak olvasni, hanem módosítani is lehet ...''Ehhez képest az Oracle szilárdan kitart amellett, hogy a fetch-ek pontosan egyszer és csakis előre haladhatnak végig az eredménysorokon. A realitáshoz alkalmazkodva az SQL2 interfészben csak a next metódust implementáltuk sqlquery-kben és rowset-ekben való pozícionálásra.
Az előbbi könyv 479. oldalán a JDBC továbbfejlesztési terveiről olvashatjuk:
,,Relációs adattáblák és Java osztályok direkt megfeleltetése: Egy adattábla minden sora a táblához rendelt osztály egy objektumpéldánya lesz, a tábla oszlopainak pedig a sorobjektumok adatmezői felelnek meg. A sorobjektumokkal történő bármilyen manipuláció esetén az azt megvalósító SQL utasítások a háttérben automatikusan végrehajtódnak. Ilyen típusú API például a Java Data Objects (JDO) specifikáció.''A tableentity objektumok is éppen így működnek, csak Jáva helyett CCC osztályokkal. A connection objektumból kapjuk a tableentity-ket, ezek select metódusaival a rowset-eket, ezekből a next metódussal a rowentity-ket (sorokat), amiknek minden oszlophoz van egy metódusuk, amivel kiolvasható, vagy átírható az oszlop értéke. Ezzel nagyjából pozícionálni tudjuk, hogy mit is nyújt az SQL2 interfész.
Megemlítem még, hogy a CCC korszerű rétegeivel dolgozunk: névtereket használunk, a felépítés teljesen objektumorientált.
Különbség van az adatbáziskezelők között abban, hogy melyik, mikor abortálja a tranzakciókat, ennek a kiegyenlítése létfontosságú. A kiegyenlítés csak az lehet, hogy az adatbáziskezelő alrendszer által jelzett hiba esetén automatikusan rollback-eljük az egész tranzakciót. Ez a jövőben bele fog kerülni a kódba.
Egyik adatbáziskezelő sem támogat egymásba skatulyázott tranzakciókat. Egy valódi alkalmazás valódi tranzakciója azonban meghív olyan résztranzakciókat, amikben szintén lehet commit, vagy elkapott kivételt követő rollback, méghozzá a külső hívó kód tudta nélkül. Ugyancsak probléma, hogy a programok több adatbáziskapcsolatra kiterjedő tranzakciókat végeznek (technikailag minden adatbáziskapcsolat külön tranzakció). Követelmény, hogy az alkalmazások átláthatóan biztosítani tudják a tranzakciók összehangolt commit-ját, rollback-jét. Ehhez szükség lesz egy tranzakciós alrendszerre.
Itt arról lesz szó, hogyan vegyük birtokba az SQL2 szoftvert.
Először is kell legyen tesztelésre alkalmas adatbázisszerverünk. A függelékekben le van írva, hogyan tudunk Linuxon Oracle-t és/vagy Postgrest installálni. Aki Windowson akar dolgozni, sajnos magára van utalva. Az Oracle-lel Windowson sem lehet gond, és a Postgres 8.0 már Windowson is megy.
Akinek már eleve van adatbázisszervere, az is nézze meg a függelékeket, ott ui. az is le van írva, hogy milyen tablespace-t, user-t, schema-t kell létrehozni az adatbázisban, hogy a demó programok működhessenek. A demó programok számítanak a konto sémára, és elszállnak, ha az nem létezik.
A CCC programokat fordító/futtató gépen szükségünk lesz az adatbáziskezelők fejlesztő/kliens környezetére is.
Szükségünk lesz a legfrisebb CCC környezetre, ez letölthető a webről http://ok.comfirm.hu/ccc2/download. Mivel a Flex és Lemon nem olyan régen tartozik csak bele a CCC környezetbe, külön megemlítem, hogy a CCC-vel telepíteni kell a Flexet és a Lemont.
Le kell fordítani az egész SQL2 projektet. A fenti előkészületek után Linuxon ez nem lehet probléma, az sql2 directoryban elindítjuk
mkall.b
Ez mindenhova benéz, és elindítja a fordítást végző Build scripteket (Linuxon). Aki Windowson dolgozik, mkall.b helyett az mkall.bat scriptet indítsa el. Meggyőződünk arról, hogy a fordítás hibamentes. Csak a Postgres developer csomag installálásával lehet gond
Az sql2/test/testdata directoryban van néhány bt tábla, amik tesztadatokat tartalmaznak. A tview programmal meg tudjuk nézni ezek tartalmát.
Az sql2/test/testdata_import directoryban megtaláljuk a btimport.exe programot. Ez az előbbi bt táblákat importálja az SQL adatbázis szerverbe:
Ha már vannak tesztadataink, akkor átfáradunk az sql2/test/basicdemo directoryba. Itt azt fogjuk tapasztalni, hogy a demóprogramok már mind le vannak fordulva, méghozzá két verzióban:
Hangsúlyozom, hogy a példaprogramok a dokumentáció lényeges részét képezik. Sok munkám van abban, hogy a példákból és a közéjük írt megjegyzésekből megfelelően domborodjon a mondanivaló. A példaprogramokat tehát el kell olvasni, és ki kell próbálni, miközben a kódot összevetjük az eredményekkel. Sajnos ez időt és fáradságot igényel.
Felhívom a figyelmet az SQLDEBUG környezeti változóra. Ha ez be van állítva,
export SQLDEBUG=on
akkor az SQL2 interfész listázza az adatbázisszervernek küldött
SQL utasításokat. Ezek vizsgálata rendkívül sokat segít a program
működésének megértésében, a hibák kijavításában.
A sql2/test/entitybrowse directoryban egy olyan demó van, ami Jáva terminálban browse-ol egy tableentity objektumot. A program nem bonyolult, de csak akkor fog lefordulni, ha a CCC környezetben van Jáva terminál támogatás, ezért kezdetben kihagyjuk ennek a projektnek a fordítását.
Egy tableentity objektum SQL select utasításokkal készített, azonos struktúrájú, de különféleképpen szűrt és rendezett eredménytábla halmazt képvisel. (Másképp: SQL select utasítások, amikben ugyanazt a joint, különféle where és order by záradékokkal kombináljuk.) Az SQL select tartalmazhat egy vagy több elemi táblát, vagy nézetet (view-t).
A tableentity objektumnak vannak select metódusai, ezek egy-egy rowset objektumot adnak. A különféle select metódusok különféle szűréseket végeznek, ennek megfelelően a rowsetek ugyanannak az (esetleg összetett) alaptáblának különféle részhalmazait tartalmazzák. A rowset objektum next metódusával lehet megkapni az eredménysorokat, a sorokat rowentity objektum formájában kapjuk.
A tableentity objektumnak van egy find metódusa, ami egy kulcsokkal maghatározott sort betölt egy rowentity objektumba. (A select speciális eseteként egyelemű rowsetet állít elő, és az egyetlen elemből rögtön rowentityt készít.)
Az adatbázisrekord, vagy select eredménytábla sorának programbeli leképezése a rowentity objektum. A rowentity objektum a kulcsmezők értékével (primary key) kapcsolódik a neki megfelelő adatbázis rekordokhoz. A tableentity minden oszlopához tartozik a rowentitynek egy azonos nevű metódusa, amivel az oszlop (mező) értéke lekérdezhető, módosítható. A rowentity nem egy rekordpointer vagy kurzor, hanem önállóan létező objektum. A program ugyananabból a táblából egyszerre több rowentityt is készíthet, azokat egymástól függetlenül módosíthatja, tárolhatja, törölheti.
A rowset objektumokat a tableentity select metódusaival kapjuk. Ugyanabból a tableentity objektumból származó rowsetekben közös, hogy ugyanazokat az elemi táblákat tartalmazzák, és a táblák ugyanúgy vannak összekapcsolva. A közös alaptáblából a különféle select metódusok különféle filterezettségű (where) és rendezettségű (order by) rowseteket adnak, amikből viszont egyforma struktúrájú rowentityket kapunk. A rowset:next metódushívással lehet megkapni a sorokat. A next a tábla elejétől a végéig haladva egyesével adogatja a sorokat (rowentityket), ha a sorok elfogytak, akkor NIL-t. Visszafelé haladni, vagy bármi egyéb módon pozícionálni nem lehet.
A tableenitynek rendelkeznie kell elsődleges kulccsal (primary key). Ez azon oszlopok felsorolásából áll, amely oszlopok értékének megadásával egyértelműen azonosítani lehet a sort, azaz a rowentity objektumot. A primary key-ben felsorolt oszlopoknak nem szabad null értéket megengedni, ezenkívül az egyediséget unique indexszel ki kell kényszeríteni.
A tableentity oszlopainak megadására columndef objektumokat használunk.
A columnref objektumokat olyan oszlopok leírására használjuk, amik nem szerepelnek a tableentity oszlopai között, viszont a tableentity valamilyen módon hivatkozik rájuk egy from, where, vagy order by záradékban.
Az indexdef objektumokkal indexek leírását közöljük a tableentityvel. A tableentity:create metódus (a tábla kreálása után) létrehozza a megadott indexeket is. Ha az indexünk unique minősítésű, akkor azzal kikényszeríthetjük sorok egyediségét, mint ahogy azt a primary key esetében is megtesszük.
Az SQL-ben az indexek léte, nemléte, nem befolyásolja a select utasítások eredményét, legfeljebb a végrehajtás hatékonyságát. Az adatbázisszerver mindig saját hatáskörben dönt egy index használatáról, mellőzéséről, sőt létrehozásáról.
Előzetes megjegyzés:
Az eredeti koncepció az volt, hogy a tableentityket XML leírásból készítjük, és ez most is működik. A tds-ekkel teljesen azonos tartalmú, de XML szintaktikájú fájlokból (ted kiterjesztéssel) a ted2prg utility ugyanazt a kódot generálja, mint a tds2prg. Az XML azonban inkább gépi feldolgozásnál előnyös, míg a humán felhasználó könnyebben elboldogul a tds szintaktikával. Ezért a tds2prg program (a ted2prg-hez képest) kiegészült egy előzetes elemzéssel, minek során ugyanazt a DOM-ot építi fel, amit a ted2prg XML-ből. Mindkét esetben a közös DOM-ból ugyanaz a modul generálja a kódot.
A Table Definition Script (tds) bekezdésekből áll. Minden bekezdés egy sor elején álló kulcsszóval kezdődik, amit szóköz nélkül kettőspont követ. Az érvényes kulcsszavak: name, version, table, join, column, colref, primkey, index, select, comment. A bekezdések a következő bekezdésig, vagy a filé végéig tartanak. A bekezdések sorrendje nem kötött. Az elemző a comment bekezdéseket kihagyja.
Névtér, amibe a tableentityNew() objektumgyártó függvény kerül.
name: multi.level.namespace
A tds-ből generált tableentityt így tudjuk legyártani:
tableentity:=multi.level.namespace.tableentityNew(con)
A bekezdés tartalma egy tetszőleges tartalmú string (idézőjelekkel), amiből a tableentity objektum version attribútumának értéke lesz.
version: "arbitrary text"
Legalább egy table bekezdésnek kell lennie, ezek alakja:
table: real.qualified.tablename=aliasname
A script más részeiben a táblára kizárólag az alias névvel
hivatkozunk. Ha nincs megadva join bekezdés, akkor a table
bekezdésekben magadott táblák Descartes-féle szorzata lesz
a tableentity alaptáblája.
Ha csak egy table bekezdés van, akkor triviálisan az abban
megadott tábla lesz az alaptábla.
Opcionális bekezdés. Ha nincs megadva, akkor a táblák Descartes-féle szorzata lesz az alaptábla. Ha meg van adva, akkor a join bekezdés tartalmából készül a tableentity által generált SQL select utasítások from záradéka (tehát lényegében egy from záradékot írunk a joinba). A from záradék SQL-beli szintaktikájától annyiban térünk el, hogy a táblákra és oszlopokra kizárólag aliasokkal hivatkozunk. Példa:
join:
a
full join b on id_a=id_b
left join c on name_a=name_c
A kettőspont utáni első szóközökkel határolt szó egyszerre
A második szóközökkel határolt egység a típusleképezés. Ebben azt a típust kell megadni, amiben a CCC program kéri/adja az oszlop adatait (függetlenül attól, hogy mi az oszlop tényleges SQL adattípusa). Az érvényes típusok:
| Cw | : w hoszúságú string |
| Nw | : w helyiértéken tárolt egész |
| Nw.d | : w helyiértéken tárolt, d tizedesjegyet tartalmazó szám |
| Nw,d | : w helyiértéken tárolt, d tizedesjegyet tartalmazó szám |
| D | : dátum |
| L | : logikai érték |
| M | : memó |
Az oszlopnevet és típust kiegészítő adatok követhetik. A kiegészítő adatok opcionálisak, és a sorrendjük nem kötött.
A t=table_alias_name alakú kiegészítéssel megadhatjuk, hogy az oszlop melyik táblából való.
A c=real_column_name alakú kiegészítéssel megadhatjuk, hogy az oszlopnak mi a valódi neve a táblában.
A nn kiegészítéssel előírjuk, hogy a tábla kreálásakor az oszlop not null minősítést kapjon.
A kettőspont utáni első szóközökkel határolt szó alias név, amivel a tds más részeiben hivatkozunk az oszlopra.
Az oszlopnevet kiegészítő adatok követhetik. A kiegészítő adatok opcionálisak, és a sorrendjük nem kötött.
A t=table_alias_name alakú kiegészítéssel megadhatjuk, hogy az oszlop melyik táblából való.
A c=real_column_name alakú kiegészítéssel megadhatjuk, hogy az oszlopnak mi a valódi neve a táblában.
Megjegyzés: Lehetnek olyan oszlopok, amikre hivatkozni akarunk a tds-ben, de nem akarjuk lekérdezni az értéküket. Az ilyenek leírását helyezzük colref bekezdésbe, ami ugyanolyan, mint a column, kivéve, hogy nincs benne típus információ.
A primkey bekezdés kötelező. Benne vesszőkkel elválasztott listában felsoroljuk azokat az oszlopokat (column bekezdésben definiált alias neveket), amik egyértelműen azonosítják a rekordokat. Az alkalmazás felelőssége, hogy az azonosítás, és ezáltal az (adatbázis rekord,rowentity objektum) megfeleltetés egyértelmű legyen.
primkey: column_alias1,column_alias2,...
Az opcionális index bekezdések formája:
index: index_name(column_alias1,column_alias2,...) [unique]
Az index bekezdésekben indexeket definiálhatunk
a táblára, ezzel bizonyos lekérdezéseket gyorsíthatunk,
illetve a unique indexekkel kikényszeríthetjük rekordok
egyediségét.
Az opcionális select bekezdések első szóközökkel határolt szavából a tableentity metódusneve lesz.
A metódusnevet követheti a where kulcsszó, majd tetszőleges SQL kifejezés, amit az SQL a where és order by kulcsszavak között elfogad. Az oszlopokra kizárólag az oszlop aliasokkal szabad hivatkozni. A kifejezés :1, :2,... alakú szimbólumokat tartalmazhat, amik helyére az SQL select parancs generálásakor paramétereket fogunk helyettesíteni.
A where feltételt követheti az order kulcsszó, ami után zárójelek között fel kell sorolni azokat az oszlopokat, amik szerint rendezni akarjuk az eredménytáblát. A felsorolásban az oszlopnevek után opcionálisan megjelenhet az asc/desc kiegészítés, ami előírja, hogy az adott oszlop szerint növekvő vagy csökkenő rendezettséget akarunk.
A where és order záradék közül legalább az egyiknek léteznie kell.
Példa:
select: select_a
where col_a_alias like :1
order(col_a_alias, col_b_alias desc)
Ezután a tableentity:select_a("a%") metódushívás kiválasztja az alaptábla azon sorait, melyekben col_a első karaktere "a", és a sorokat rendezi col_a, majd csökkenő sorrendben col_b szerint.
Nézzük az alábbi példát az sql2/test/basicdemo/tds/proba.tds-ből:
name: proba
version: "1,2005-02-21"
table: konto.proba=p
column: szamlaszam C24 nn t=p c=szamla
column: devnem C3
column: nev C30 c=megnevezes
column: egyenleg N20.2 c=osszeg
column: tulmenflag L
column: konyvkelt D
column: megjegyzes M
primkey: szamlaszam,devnem
index: nev(nev,szamlaszam)
select: select_kk
where konyvkelt<:1 or konyvkelt is null
order(szamlaszam)
select: select_ge
where szamlaszam>=:1
order(nev desc,szamlaszam)
Ebből a scriptből a tds2prg kódgenerátorral programot készítünk:
tds2prg.exe proba.tds
Kapunk egy prg-t, amit a szokásos módon befordítunk a programunkba.
A prg-ben van definiálva a proba.tableEntityNew()
objektumgyártó függvény. Nézzünk egy-két programozási mintát,
mire használható.
Az objektumgyártó egy tableentity objektumot ad, amivel a con adatbáziskapcsolaton át elérhető konto.proba táblát manipulálhatjuk. A konto.proba név a tds script table bekezdéséből jön. Valójában nem tudjuk, hogy a név mögött tábla van-e, vagy view, mert a két eset formailag nem különbözik. A tábla (view) neve lehet minősített. A jelen esetben a séma neve (ami a táblát tartalmazza) ,,konto'', a tábla vagy view neve ,,proba''. A tds script name bekezdésében egy névtér van megadva, esetünkben ,,proba''. Az itt megadott névtérbe (ami lehetne többszintű is) kerül az objektumgyártó tableEntityNew() függvény, amit tehát így kell meghívni:
tableentity:=proba.tableEntityNew(con)
A tableEntityNew()-nak van egy második, opcionális paramétere is. Ha ezt megadjuk, akkor a konto.proba helyett a paraméterként megadott masik.tabla-hoz kapunk hozzáférést. Tételezzük fel, hogy ennek a másik táblának hasonló szerkezete van, mint a konto.proba-nak:
tableentity:=proba.tableEntityNew(con,{"masik.tabla"})
A táblanév egy lista (array) elemeként van megadva. Mint később
látni fogjuk egy tds script több táblára is hivatkozhat,
tehát több tábla paraméternek is értelme lehet, ezért a
táblákat egy arrayben felsorolva kell megadni.
Ezen a ponton nem tudjuk, hogy az adatbázisban van-e egyáltalán ilyen tábla, egyelőre csak egy programbeli objektumunk van. Ha emögött még nincs tábla, akkor most létre tudjuk hozni:
tableentity:create
Ez persze csak akkor értelmes, ha valóban tábláról van szó,
nem pedig view-ról, ui. az interfész mindenképpen egy
create table utasítást fog küldeni az adatbázisszervernek,
view-król mit sem tud. Természetesen a konto.proba,
vagy masik.tabla tábla fog kreálódni, ahogy a tableentity
gyártásakor paraméterként megadtuk.
Itt egy kicsit érdemes elidőzni, megtárgyalni, milyen lesz az új tábla. Általában olyan nevű oszlopok lesznek benne, mint a tds scriptben az oszlopok neve, de vannak kivételek. A példában van egy ,,nev'' nevű oszlop, amiből a tableentity sorobjektumainak lesz egy ,,nev'' nevű metódusa. A táblában azonban a c=megnevezes előírás alapján ennek az oszlopnak az adatbázisbeli neve ,,megnevezes''. Nem tudjuk pontosan, hogy mi az oszlopok SQL adattípusa, de bízhatunk benne, hogy az interfész olyan adattípust választ, ami kompatibilis a tds scriptben előírt Clipper típussal. A nev esetében sejthető a varchar(30), de pl. boolean típus egyes adatbáziskezelőkben van, másokban nincs, ezért nem egyértelmű, mi a Clipper L típus SQL megfelelője. Az nn-nel jelölt oszlopok not null minősítést kapnak. Az interfész a tábla elkészítésekor egyúttal indexeket kreál a primkey és index bekezdések szerint. A unique indexeknek szerepük lehet sorok egyediségének kikényszerítésében.
Kissé más a helyzet, ha az adatbázistáblát nem a tableentity interfésszel csináljuk, hanem készen kapjuk. A megrendelő bank alapadatait tartalmazó táblát pl. biztosan nem mi fogjuk kreálni. Ilyenkor egyáltalán nem tudjuk befolyásolni az oszlopok típusát, azonban az interfész oda-vissza elvégzi az SQL típus és a Clipper típus közötti konrverziót, ha egyáltalán lehetséges. Sajnos ilyenkor az egyediséget nem tudjuk indexekkel biztosítani, sem a not null feltételeket betartatni, ha az eleve nincs benne az adatdefiníciókban.
Ha a táblát meg akarjuk szüntetni, megtehetjük a
tableentity:drop
metódushívással. Ez egy drop table utasítást küld a szervernek,
(view-ra nyilván nem működik).
Térjünk rá az adatok lekérdezésére. A tableentity objektumnak mindig van egy select metódusa, ami az alaptábla összes sorát tartalmazó rowset objektumot ad.
rowset:=tableentity:select
A rowset objektum legfontosabb metódusa a next, ezzel egyesével, előrefelé haladva lekérhetjük a sorokat:
while( (rowentity:=rowset:next)!=NIL )
...
end
rowset:close
Amíg van újabb sor, addig rowset:next egy rowentity objektumot ad,
ha már nincs több sor, akkor NIL-t. Ügyelni kell a rowset-ek lezárásra,
máskülönben elfogynak bizonyos erőforrások (handlerek).
Most már soraink is vannak, nézzük a mezőértékeket:
r:=rowentity
? r:szamlaszam, r:devnem, r:nev, r:egyenleg
Pontosan mit is írtunk ki? A
konto.proba.szamla,
konto.proba.devnem,
konto.proba.megnevezes,
konto.proba.osszeg nevű adatbázismezőket.
A rowentity objektumok tehát olyan attribútumokkal rendelkeznek,
mint amilyen oszlopnevek vannak a tds scriptben. Ha nem intézkedünk
másképp, akkor ezek az adattábla azonos nevű oszlopát jelentik,
de ezt felülbírálhatjuk a c=real_column_name előírással.
A CCC programból nézve a rowentity attribútumoknak olyan típusuk van, mint amit a tds scriptben előírtunk. Pl. az alábbi programrészlet végrehajtása után
rowentity:nev:=""
? valtype(rowentity:nev), strtran(rowentity:nev," ","x")
ezt látjuk kiírva: C xxx...xxx (30 darab x).
Az adatbázisból kaphatunk SQL null értékeket is. E tekintetben
a Clipper hagyományokat követjük, és az interfésztől
a nullok helyén nem NIL-t kapunk, hanem egy megfelelő típusú empty értéket.
Ha mindenképpen szükség van a nullok vizsgálatára, arra is van mód,
lásd a osztály referenciát és a példaprogramokat.
Tegyük fel, hogy van egy '111111112222222233333333' számlaszámú, 'HUF' devizanemű sorunk a táblában, ezt a
rowentity:=tableentity:find({'111111112222222233333333','HUF'})
metódushívással tudjuk beolvasni. Ha mégsincs sor a megadott
számlaszámmal, akkor tableentity:find NIL-t ad.
A tds scripttől függetlenül a tableentitynek mindig van egy általános select metódusa, ami az alaptábla összes sorát tartalmazó rowsetet ad, és egy find metódusa, ami a primary key alapján kiválaszt egyetlen sort. Lehetnek azonban más select metódusok is, ha definiálunk ilyeneket a tds-ben. A jelen példában a
rowset:=tableentity:select_kk({stod("20000101")})
metódushívás kigyűjti azokat a sorokat, amikben a könyvelés
dátuma régebbi, mint 2000-01-01, vagy egyáltalán nem volt rajta
könyvelés, és ezért a dátum null. Az interfész a tds-ben megadott
where záradékból SQL where záradékot készít úgy, hogy a select metódus
array paraméterében felsorolt értékeket behelyettesíti az
:1, :2,... szimbólumok helyére. A tds-ben
bármit megadhatunk a where záradék helyén, amit az SQL elfogad
a ,,where'' és az ,,order by'' kulcsszavak között.
A tds-beli order záradékból SQL order by záradék lesz.
Az interfész az order zárójelei közötti szöveget egyszerűen a
generált SQL parancs ,,order by'' kulcsszava után írja.
A gyakorlatban általában a zárójelek között felsoroljuk
az oszlopokat, amik szerint rendezni akarunk, esetleg az
asc/desc módosítással kiegészítve.
Rátérve a módosítások tárgyalására mégegyszer megjegyezzük, hogy nem minden tábla módosítható. Ha az alaptábla egy view, vagy több táblából képzett join, akkor a módosítási kísérlet valószínűleg hibát eredményez. Rakjunk most be egy új sort a táblába:
rowentity:=tableentity:instance
rowentity:szamlaszam:="XXXXXXXXYYYYYYYYZZZZZZZZ"
rowentity:devnem:="EUR"
rowentity:egyenleg:=100000
rowentity:insert
con:sqlcommit
A tableentity objektum instance metódusa gyárt nekünk egy
üres rowentityt, amit feltöltünk adatokkal. A rowentity insert
metódusa generál egy insert into utasítást, amivel kiírja a rekordot.
A kiírást a commit véglegesíti. Tegyük fel, hogy ugyanezt
a rekordot módosítani kell:
rowentity:=tableentity:find({'XXXXXXXXYYYYYYYYZZZZZZZZ','EUR'})
rowentity:egyenleg+=1000
rowentity:update
con:sqlcommit
Most az interfész generálni fog egy ilyen utasítást
update "KONTO"."PROBA" set "OSSZEG"=101000
where "SZAMLA"='XXXXXXXXYYYYYYYYZZZZZZZZ' and "DEVNEM"='EUR'
amivel módosul a rekord. Rendkívül fontos észrevétel, hogy
a rowentity objektum, a primary key oszlopok egyezése alapján
találja meg a hozzá tartozó rekordot. Mi történik akkor
a következők után?
rowentity:szamlaszam:="valami más érték" //ROSSZ!
rowentity:update //ROSSZ!
Így nem az eredeti rekord módosul, hanem a szerver minden olyan
rekordot módosít, aminek a számlaszáma a "valami más érték".
Lehet, hogy semmi sem módosul, lehet, hogy egy olyan rekord,
amire nem számítottunk. A tanulság, hogy a primary key-t alkotó
oszlopokat nem módosíthatjuk közvetlenül.
Szerencsére a gyakorlatban erre ritkán van szükség. Ha mégis,
akkor ezt csináljuk:
rowentity:=tableentity:find({'XXXXXXXXYYYYYYYYZZZZZZZZ','EUR'})
rowentity:delete
rowentity:szamlaszam:="valami más érték"
rowentity:insert
con:sqlcommit
Mivel a két művelet egy tranzakcióban van, nem fenyeget,
hogy csak az egyik hajtódik végre, a másik pedig nem.
A példa alapján világos: az alkalmazásnak létfontosságú,
hogy a primary key egyedisége megmaradjon, így az adatbázisrekordok
és a program rowentity objektumai közötti megfeleltetés ne sérüljön.
A rowentity lényeges tulajdonsága, hogy igazi objektum, nem pedig csak holmi rekordpointer vagy kurzor. Az objektum az adatait saját memóriabufferben tárolja, ami nem szűnik meg attól, hogy végrehajtunk egy újabb rowset:next-et, rowset:close-t, vagy akár con:rollback-et. A program változóiban tárolhatunk egyidejűleg akárhány rowentity objektumot.
Ebben az alfejezetben egy olyan példát elemzünk, amiben a tableentity alaptáblája több elemi tábla összekapcsolásával keletkező join. Az ilyen tableentity objektumoknak is vannak módosító metódusai (create, drop, zap, insert, delete, update), ám ezek meghívása nagy valószínűséggel hibát okoz, erre többet nem is térünk ki. A példa az sql2/test/basicdemo/tds/probaszerencse.tds-ből való.
name: probaszerencse
version: "1,2005-02-21"
table: konto.proba=p
table: konto.szerencse=q
join: p left join q on szamlaszam=qszamlaszam
column: szamlaszam C24 t=p c=szamla
column: devnem C3
column: nev C30 c=megnevezes
column: egyenleg N20.2 c=osszeg
column: tulmenflag L
column: konyvkelt D
column: megjegyzes M
column: kiegdata C20
colref: qszamlaszam t=q c=szamla
primkey: szamlaszam,devnem
Előzőleg a konto.proba nevű táblával dolgoztunk, most ezt kiegészítjük a konto.szerencse táblával. A két tábla a szamla nevű oszlopon keresztül kapcsolódik. A szerencse nevű táblából a kiegdata oszlopot akarjuk hozzávenni a tableentityhez (nem túl sok, de most nem az a lényeg, hanem maga az összekapcsolás).
Először is megfigyeljük, hogy a tableentity objektum gyártó függvény most a probaszerencse névtérbe van helyezve, tehát így lehet meghívni:
tableentity:=probaszerencse.tableEntityNew(con)
Látjuk, hogy a tds-ben két table bekezdés is van.
Ezek megadják az alapesetben használt táblákat, és azok
alias neveit (p és q). A script más részeiben a táblákra
kizárólag az alias nevekkel hivatkozunk.
Tudjuk, hogy szükség esetén a tds-ben meghatározott táblákat helyettesíthetjük más táblákkal, például:
tablist:={"masik.proba","masik.szerencse"}
tableentity:=probaszerencse.tableEntityNew(con,tablist)
A helyettesítendő táblákat utólag is megadhatjuk:
tableentity:=probaszerencse.tableEntityNew(con)
tableentity:tablist:={"masik.proba","masik.szerencse"}
A két módszer ugyanarra az eredményre vezet.
A table definition script (tds) join bekezdésében határozzuk meg, hogyan legyen a két tábla összekapcsolva. Ha ugyanezt a joint közönséges SQL-ben akarnánk megcsinálni, pl. az sqlplus-ban, akkor valami ilyesmit írnánk (rövidítésekkel):
select
p.szamla,
devnem,
...
kiegdata
from
konto.proba p
left join konto.szerencse q
on p.szamla=q.szamla
order by
p.szamla,devnem
Koncentráljunk a from záradékra. A tds-beli join bekezdés úgy
vezethető le az SQL from záradékból, hogy a redundáns információt
kihagyjuk. A konto.proba p helyett a tds joinban csak annyit
írunk: p, hiszen a table bekezdésből már tudjuk, hogy a p alias
név a konto.proba táblát jelenti.
A p.szamla helyett azt írjuk szamlaszam,
a q.szamla helyett pedig qszamlaszam,
mert a column és colref bekezdések meghatározzák,
hogy ezek a nevek pontosan melyik oszlopot jelentik.
Látható a column bekezdések kettős szerepe.
Egyrészt metódusneveket jelentenek, másrészt oszlop alias
neveket hoznak létre, amiket a tds scriptben használunk.
A colref bekezdéseknek csak oszlop alias szerepe van.
Foglaljuk össze a table és join bekezdésekkel kapcsolatos
tudnivalókat:
1) A table bekezdések sorolják fel a tableentityben szereplő elemi táblákat. Minden táblához rendelünk egy alias nevet.
2) A join bekezdés opcionális (ha csak egy tábla van, akkor a joinnak nem is volna értelme). Ha nincs join, akkor a tableentity alaptáblája az elemi táblák Descartes-féle szorzata, vagyis ilyenkor az interfész olyan SQL select parancsot generál, aminek a from záradékában egyszerűen fel vannak sorolva a táblák.
3) Ha van join bekezdés, akkor oda egy SQL select from záradékot írunk azzal az eltéréssel, hogy a táblanevek helyett tábla aliast, az oszlopnevek helyett pedig mindenhol oszlop aliast írunk.
Az a fajta from záradék, amit itt használunk az SQL92 szabvánnyal került be az SQL-be. Korábban csak az ún. inner join volt ismert (a where záradékban). Az újabb szabvány kiegészült az outer joinnal, és a különféle joinok összes variációinak egységes értelmezésével, de ezeket a from záradékba kell írni. Sajnos ebben a dokumentációban nincs hely az SQL mélyebb ismertetésére.
Folytassuk az oszlopokkal. Az első column bekezdésben ezt látjuk:
column: szamlaszam C24 t=p c=szamla
Szó volt már a szamlaszam név szerepéről. A tableentityből származó rowentity (sor)objektumoknak lesz egy ilyen nevű attribútuma, egyúttal bevezet egy oszlop aliast, amivel a tds-ben bárhol (join, where, order) hivatkozni tudunk az oszlopra.
A C24 típus értelmezése: Nem érdekel minket, hogy az adott oszlop SQL adattípusa pontosan micsoda. Akármi is az SQL adattípus, a mi programunk C24-re konvertálva kéri az adatot a szervertől. Mármost a tényleges típus és a szerver tudása dönti el, hogy ez az esetleges konverzió értelmes-e, lehetséges-e egyáltalán. Ha nem értelmes, vagy nem lehetséges, akkor az az alkalmazás logikai, vagy közvetlen futási hibáját fogja okozni.
A c=szamla kiegészítő adat azt mondja: A szamlaszam metódusnévhez és oszlop aliashoz valójában a tábla szamla oszlopa tartozik. Ha ez nem volna megadva, akkor az interfész és az adatbázis szerver szamlaszam nevű oszlopot keresne, és hibát okozna, ha az nem létezik.
Esetünkben azonban nemcsak a proba táblában van szamla nevű oszlop, hanem a szerencse-ben is. Ezért azt is meg kell mondanunk, hogy melyik táblából kell venni a szamla oszlopot. Erre a célra szolgál a t=p előírás.
A column bekezdéssel kapcsolatban még tudni kell, hogy a kettőspont utáni első szóközökkel határolt szó kötelezően az alias név, a második szó kötelezően a típus, a további adatok opcionálisak, és sorrendjük tetszőleges.
A colref bekezdésekkel olyan oszlopokhoz készítünk oszlop aliast, amiket nem akarunk bevenni a tableentity alaptáblájába, de hivatkoznunk kell rá a join-ban, where-ben, vagy order-ben. A colref szintaktikája lényegében ugyanaz, mint a columné, csak kimarad belőle a típusmeghatározás.
A további összetevőket, mint a primkey, különféle selectek, már ismerjük. Indexdefiníciókat helyezni egy többtáblás tds-be értelmetlen, mivel soha nem fogjuk a táblát kreálni. A lekérdezések ugyanúgy működnek, mint az egytáblás esetben, például:
tableentity:=probaszerencse.tableEntityNew(con)
rowset:=tableentity:select
while( (r:=rowset:next)!=NIL )
? r:szamlaszam,r:kiegdata
end
rowset:close
Aszerint, hogy melyik névtérből hívjuk meg az sqlconnectionNew() objektumgyártó függvényt, Oracle vagy Postgres adatbáziskapcsolathoz jutunk:
con_ora:=sql2.oracle.sqlconnectionNew(connect_string)
con_pg:=sql2.postgres.sqlconnectionNew(connect_string)
A connect_string paraméter opcionális. Oracle esetében
a connect string tartalma a megszokott user@dbsid/password alakú,
ahogy azt pl. az sqlplus is várja. Postgres esetén a connect string
tartalma pontosan az lehet, mint amit a PQconnectdb függvény
(a libpq klienskönyvtárból) elfogad. Ha a connect string
nincs egyáltalán megadva, akkor a program az ORACLE_CONNECT, illetve
POSTGRES_CONNECT környezeti változók tartalmát használja
bejelentkezéshez, például:
export ORACLE_CONNECT=scott@database/tiger
export POSTGRES_CONNECT="host=HH dbname=DD user=UU password=PP"
A sqlconnection objektum létrehozásakor azonnal megtörténik
a bejelentkezés. A metódusok:
Az sqlquery objektumok a con:sqlqueryNew(select_stmt) objektumgyártó metódushívással keletkeznek. A metódusok:
A connection osztály sqlsequencenew metódusával kapunk új sqlsequence objektumot: con:sqlsequenceNew(name), ahol name az adatbázisbeli sequence objektum neve. Ne feledjük, az objektumgyártó csak programobjektumot készít, az adatbázisban van, vagy nincs hozzá tartozó sequence objektum. A metódusok:
Az absztrakt tableentity osztály a közös őse minden konkrét tableentitynek. Az alábbi felsorolás csak az alkalmazási programok számára érdekes metódusokat tartalmaza.
t:tablist:={"tab1=talias1","tab2=talias2",...}
ahol
tab1,... az adattáblák konkrét neve,
talias1,... pedig a táblák hivatkozási neve.
A tableentity objektum többi részében, ahol az szükséges
(pl. amikor meg kell jelölnünk, hogy egy oszlop melyik táblából való)
mindig a tábla alias nevét használjuk. Ennek eredményeként
egy tableentity osztály invariáns a benne szereplő táblákra,
azaz a tablist attribútum átírásával ugyanaz az objektum
és adatstruktúra más fizikai táblákra is alkalmazható.
Az alias nevek alkalmazása nem kötelező, nélkülük azonban elvész az invariancia. Ha a tableentity csak egyetlen táblát tartalmaz, akkor a tableentity definícióban sehol sincs szükség táblahivatkozásra, hiszen mindig a tablist egyetlen eleméről lehet csak szó, ilyenkor sem szükséges alias nevet használni.
Az absztrakt tableentity osztályt oszlopokkal és select metódusokkal bővítve kapjuk a konkrét tableentity osztályokat. Ezek kódját a gyakorlatban nem kézzel írjuk, hanem XML leírásból, vagy tds scriptből programmal generáljuk. A generált kód a tableentityre jellemző névtérbe helyezi az objektumgyártó függvényt, amit így hívhatunk meg:
tableentity:=multi.level.namespace.tableentityNew(con)
A __method__ alakú metódusokat az alkalmazási programok közvetlenül nem használják, csak a tanulság kedvéért szerepelnek az ismertetésben.
p left join q on szamlaszam=qszamlaszam
A find és select metódusokkal lehet a tábla kiválasztott sorait lockolni.
Megjegyzés: Postgresben nincs timeout támogatás, ezért minden lock végtelen ideig, vagy a deadlock detektálásáig vár.
szamlaszam like :1Legyen az opcionális selectünk neve select_like, akkor a metódust így hívhatjuk:
rowset:=t:select_like({'1111111122222222%'})
Ez egy olyan rowsetet ad, amiben a számlaszám 1-16 jegyei
a megadott mintához illeszkednek.
A select* metódusok első paramétere egy array, amiben a where záradék sorszámozott paraméterei helyére helyettesítendő (bind) értékek vannak felsorolva (az automatikus selectnél ezt a paramétert üresen kell hagyni).
A select* metódusok második paraméterével a lockolás szabályozható:
Megjegyzés: Postgresben nincs timeout támogatás, ezért minden lock végtelen ideig, vagy a deadlock detektálásáig vár.
A lockolás független a sorok beolvasásától, azaz a rowset objektum létrejöttekor (még az első next előtt) az összes sor már lockolva van. A lockolt sorokat más tranzakció nem lockolhatja és nem módosíthatja. A lockot csak a tranzakció vége (commit/rollback) oldja fel, pl. nem oldja fel a lockot a rowset lezárása.
A rowset objektumokat az alkalmazás sosem közvetlenül hozza létre, hanem a tableentityk select metódusainak értékeként kapja. Egy rowset objektum egy feldolgozás alatt álló SQL select utasítást képvisel. A select utasítással (rowsettel) mindössze két dolgot lehet csinálni:
Az adatbázisrekord, vagy az SQL select eredménytábla egy sorának programbeli képe a rowentity objektum. Háromféleképpen lehet rowentity objektumhoz jutni:
A tableentity minden oszlopához létezik a rowentity objektumnak egy azonos nevű metódusa, amivel az adott mező értéke lekérdezhető és módosítható. A rowentity objektumok mind saját adatbufferrel rendelkeznek, emiatt egyszerre több (akárhány) azonos típusú rowentity objektumunk lehet, amikkel egymástól függetlenül végezhetünk műveleteket.
A rowentityk további metódusai:
A columnref objektumok nem kerülnek bele a tableentitybe, csak a tableentitykben levő SQL utasítások elkészítésekor kapnak segédszerepet azáltal, hogy tárolják egyes közvetlenül nem használt oszlopok adatbázisbeli azonosítóját.
Columndef objektumokkal az alkalmazási programokban ritkán találkozunk, mert az oszlopok általában csak a tableentityk belső működéséhez kellenek. A tableentity columndef objektumokkal való feltöltését sem közvetlenül végezzük, mert ezt az XML definíció alapján generált kód teszi. Ha azonban vizsgálni kell, hogy a szervertől kapott eredeti érték null, vagy nem null, mégiscsak a columndef objektumhoz kell fordulnunk. A columndef az előző columnref osztály leszármazottja, tehát rendelkezik az előbbi metódusokkal. A további metódusok:
Ha egy létező Oracle adatbázis egy oszlopát le akarjuk képezni Clipper típusra, akkor választanunk kell a Cw, Nw[.d], D, L, M típusok közül. A konverziót az adatbázisszerver fogja végezni, ha lehetséges, ha pedig nem lehetséges, akkor runtime errort kapunk. Az ,,értelmes'' konverziók általában lehetségesek.
A típusleképezés szabályai Oracle esetében:
Cw --> char(w) ha w<=8 Cw --> varchar(w) ha w>8 M --> clob Nw --> number(w) Nw,d --> number(w,d) Nw.d --> number(w,d) L --> number(1) a boolean-t nem ismeri D --> date
A típusleképezés szabályai Postgres esetében:
Cw --> char(w) ha w<=8 Cw --> varchar(w) ha w>8 M --> bytea Nw --> numeric(w) Nw,d --> numeric(w,d) Nw.d --> numeric(w,d) L --> boolean D --> date
Ez egyúttal behatárolja, hogy a tableentity interfésszel milyen struktúrájú adattáblák hozhatók létre.
Index adatok tárolására használjuk az indexdef objektumokat.
Az itt leírt eljárással Debian Sarge, Ubuntu 4.10, SuSE 9.0, SuSE 10.0 32-bites Linuxokra tudtam Oracle-t installálni.
Ellenőrizzük, hogy az alábbi csomagok installálva legyenek a rendszerünkön:
gcc, make, binutils, motif, lesstif, rpm
A hiányzó csomagokat installáljuk. A libmotif3 csomag Ubuntun a multiverse-ből installálható. SuSE-n csak libmotif2 van, lesstif pedig csak forrásból volna beszerezhető. Mindenesetre nem értem, hogy minek lesstif, ha van motif, és minek egyáltalán akármelyik, amikor a GUI megjelenítését Jáva végzi. SuSE 9.0 esetében a linker jelezni fogja a motif hiányát, de attól a rendszer még használható lesz. SuSE 10.0-án a telepítő hiányolni fogja a gcc 2.96-ot, de az installáció a gcc 4.x-szel is működni fog. Kezdetben (a runInstaller elindításáig) root-ként dolgozunk.
Létrehozzuk az alábbi csoportokat és felhasználókat:
id nobody # léteznie kell
groupadd oinstall
groupadd dba
useradd -g oinstall -G dba -d /opt/oracle oracle
passwd oracle #jelszót adunk az oracle usernek
A továbbiakban az installációt az oracle felhasználó nevében csináljuk.
Elhozzuk az Oracle-től a ship.db.cpio.gz csomagot és alkalmas helyen kibontjuk.
gunzip ship.db.cpio.gz
cpio -idmv < ship.db.cpio
Létrehozzuk az Oracle base directoryt:
mkdir /opt/oracle/base
Beírjuk /etc/profile-ba:
export ORACLE_BASE=/opt/oracle/base
export ORACLE_HOME=$ORACLE_BASE/product/10g
export TNS_ADMIN=$ORACLE_HOME/network/admin
export ORA_NLS33=$ORACLE_HOME/nls/data
export NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1
export NLS_DATE_FORMAT="YYYY-MM-DD"
export ORACLE_SID=test
PATH=$PATH:$ORACLE_HOME/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib
export PATH
export LD_LIBRARY_PATH
Beírjuk /etc/sysctl.conf-ba:
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
fs.file-max = 65536
net.ipv4.ip_local_port_range = 1024 65000
Utána futtatjuk: /sbin/sysctl -p
Beírjuk /etc/security/limits.conf-ba:
* soft nproc 2047
* hard nproc 16384
* soft nofile 1024
* hard nofile 65536
Beírjuk /etc/pam.d/login-ba:
session required /lib/security/pam_limits.so
Megcsináljuk a következő symlinkeket:
ln -s /usr/bin/awk /bin/awk
ln -s /usr/bin/rpm /bin/rpm
ln -s /usr/bin/basename /bin/basename
ln -s /etc /etc/rc.d
Létrehozzuk a /etc/redhat-release filét a következő tartalommal:
Red Hat Linux release 2.1 (drupal)
Eddig root-ként dolgoztunk, most váltunk oracle-re, és oracle-ként elindítjuk a runInstaller-t:
cd .../Disk1
./runInstaller
Itt hosszú ideig dolgozik, fordítgat, eközben adódhatnak hibák, ezek némelyike figyelmen kívül hagyható.
Az installer felszólít a root.sh script futtatására, megtesszük neki. Ezután vár 10 percet a cssd démon elindulására. Vagy várunk 10 percet, vagy küldünk egy HUP-ot az init-nek, amire az újraolvassa a konfigurációs filéit.
A root.sh script beír a /etc/inittab végére egy ilyen sort:
h2:35:respawn:/etc/init.d/init.cssd run >/dev/null 2>&1 </dev/null
A cssd démon az ASM (Automatic Storage Manager) modullal tart fenn valamilyen kommunikációt. Nálunk nincs ASM, tehát az inittab-ba írt sort kiszedhetjük, ha az installer túljutott rajta (többet nem kell).
Beírjuk /etc/oratab-ba:
test:/opt/oracle/base/product/10g:Y
Csinálunk egy /etc/init.d/oracle filét a következő tartalommal:
#!/bin/bash
#
# Run-level Startup script for the Oracle Instance and Listener
ORA_HOME="/opt/oracle/base/product/10g"
ORA_OWNR="oracle"
# if the executables do not exist -- display error
if [ ! -f $ORA_HOME/bin/dbstart -o ! -d $ORA_HOME ]
then
echo "Oracle startup: cannot start"
exit 1
fi
# depending on parameter -- startup, shutdown, restart
# of the instance and listener or usage display
case "$1" in
start)
# Oracle listener and instance startup
echo -n "Starting Oracle: "
/sbin/sysctl -p
su - $ORA_OWNR -c "$ORA_HOME/bin/lsnrctl start"
su - $ORA_OWNR -c $ORA_HOME/bin/dbstart
touch /var/lock/subsys/oracle
echo "OK"
;;
stop)
# Oracle listener and instance shutdown
echo -n "Shutdown Oracle: "
su - $ORA_OWNR -c "$ORA_HOME/bin/lsnrctl stop"
su - $ORA_OWNR -c $ORA_HOME/bin/dbshut
rm -f /var/lock/subsys/oracle
echo "OK"
;;
reload|restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 start|stop|restart|reload"
exit 1
esac
exit 0
Ezután a szokásos módon vezérelhető a szerver:
/etc/init.d/oracle start|stop|restart
A szerveren az $ORACLE_HOME/network/admin/listener.ora filében van leírva a listenerek paraméterezése. Az installáció után ez általában ,,magától'' jó.
A kliens programok számára az $ORACLE_HOME/network/admin/sqlnet.ora filében fel vannak sorolva a protokollok, amikkel hálózati szolgáltatásokat tudnak keresni. Nálam ez így néz ki:
# SQLNET.ORA Network Configuration File NAMES.DEFAULT_DOMAIN = comfirm.x NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)Tehát elsősorban a tnsnames filét nézzük, ha egy adatbázist keresünk a hálózaton. A $ORACLE_HOME/network/admin/tnsnames.ora filé egy kis adatbázist tartalmaz a hálózaton található Oracle szolgáltatásokról. Ahhoz, hogy az Oracle hálózati kliens megtalálja a ,,test'' adatbázis szolgáltatást (ez volna a SID?), el kell helyezni a tnsnames.ora filébe egy ilyen bekezdést:
test.COMFIRM.X =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 1g.comfirm.x)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = test)
)
)
A domain, host és service neveket a konkrét esetnek
megfelelően kell beírni.
A jelen demóban az adatbázis objektumok a konto schema-ban jönnek létre. Legjobb ezt a schema-t mindjárt a telepítés után létrehozni.
Elindítjuk (system-ként) az sqlplus-t, és végrehajtjuk benne a következőket:
create tablespace konto logging
datafile '/opt/oracle/base/oradata/test/konto.dbf'
size 8M reuse autoextend on next 4M
maxsize unlimited
extent management local autoallocate
segment space management auto;
create user konto
identified by konto
default tablespace konto
quota 100000M on konto;
grant connect to konto;
quit;
Ezután az Oracle adatbázis alkalmas a demó programok kiszolgálására.
Ha egy gépre Oracle szervert installálunk, azon lesz Oracle kliens is. Ha azonban csak a kliensre van szükségünk, akkor nem érdemes 100-szoros munkával szervert telepíteni.
Letöltjük az Oracle-től az Oracle Instant Client csomagokat. A szoftver 4 darab zip filéből áll (egyes platformokra rpm is van). A 4 közül a CCC-hez csak két összetevőre van szükség, a basic-re és az sqlplus-ra. A zip-eket kibontjuk, és a tartalmukat betesszük a /opt/oracle/instantclient (vagy egy tetszés szerint választott) directoryba.
Megcsináljuk a /opt/oracle/instantclient/network/admin directoryt, és abban elhelyezzük az alábbi két filét:
A sqlnet.ora filé tartalma:
# SQLNET.ORA Network Configuration File NAMES.DEFAULT_DOMAIN = comfirm.x NAMES.DIRECTORY_PATH= (TNSNAMES)
A tnsnames.ora ilyen bekezdésekből áll:
test.COMFIRM.X =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 1g.comfirm.x)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = test)
)
)
Természetesen a domain, host, service neveket a saját adatainkkal
helyettesítjük.
A /etc/profile-ba beírjuk:
export ORACLE_HOME=/opt/oracle/instantclient export PATH=$PATH:$ORACLE_HOME export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME export TNS_ADMIN=$ORACLE_HOME/network/admin export NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1 #export NLS_LANG=HUNGARIAN_HUNGARY.EE8ISO8859P2 export NLS_DATE_FORMAT="YYYY-MM-DD"Ezután újonnan bejelentkezve fut az sqlplus, és futnak a CCC programok.
Ha az sqlnet.ora és tnsnames.ora filék elhelyezkedése az ORACLE_HOME-hoz képest olyan, mint a fenti példában, akkor a TNS_ADMIN változó megadása felesleges (de nem árt).
Az adatbázisok általában Latin-1 (WE8ISO8859P1) kódkészlettel jönnek létre, mert ez a default. Ha egy ilyen adatbázissal állunk kapcsolatban, de a kliensen Latin-2 (EE8ISO8859P2) kódkészlet van beállítva, akkor a szövegmezőkben elromlanak a 128 feletti byteok. (Ha kiírunk egy 128 feletti betűkből álló stringet, akkor a visszaolvasás a legtöbb helyen kérdőjelet ad.) Ezért a kliensen kötelezően ugyanazt a kódkészletet kell beállítani, mint ami a szerveren van, akkor is, ha így a szerver üzeneteit magyar helyett esetleg angolul kapjuk.
Jelenleg a Postgres 7.4.7-es, forrásból installált változatát használom.
A 7.3.x és 7.4.x változatok között jelentős eltérés, hogy utóbbiban megszűnt a szerver oldali autocommit. Korábban a set autocommit off utasítás hatására egy commit/rollback után automatikusan indult a következő tranzakció, ami megint csak a következő commit/rollback-ig tartott. A 7.4.x-ben egyáltalán nincs set autocommit utasítás, helyette minden esetben explicite ki kell adni a begin transaction utasítást. Emiatt változtatni kellett a kliens interfészen.
Adatbázis clusternek nevezzük az adatbázisok egy olyan halmazát, amik ugyanabban a PGDATA könyvtárban vannak, és ugyanaz a Postmaster folyamat kezeli őket. Csinálunk egy üres directoryt:
mkdir pgdata
Ebben végrehajtjuk:
initdb --no-locale -D `pwd`
Létrehozzuk pgdata-ban az alábbi scripteket. A pg-start script indítja az adatbázist:
#!/bin/bash
pg_ctl start -D `pwd` -l PG_LOG
A pg-stop script leállítja az adatbázist:
#!/bin/bash
pg_ctl stop -D `pwd` -l PG_LOG
Elindítjuk a Postgres-t (az előbbi indítóscripttel), utána elindítjuk psql-t.
psql template1
A psql-ben végrehajtjuk:
create database vermes;
Ezzel a saját home-omban csináltam egy privát adatbázis clustert (adatbázisok olyan halmazát, amit ugyanaz a Postmaster kezel), ebben a clusterben jelenleg egy adatbázis van (vermes), amibe azonosítás nélkül (mint lokális UNIX user) bármikor beléphetek, és amiben bármit csinálhatok.
Mivel a jelen demó minden adatbázisobjektumot a konto schema-ban hoz létre, legjobb ezt a schema-t mindjárt megcsinálni. A psql-ben végrehajtjuk:
create schema konto;
Ezután a Postgres adatbázis alkalmas a demó programok kiszolgálására.
1ComFirm BT.