SQL2 1.0 interfész

Dr. Vermes Mátyás1

2005. február

1.  Áttekintés
2.  Kezdőlépések
3.  Tableentityk használata
    3.1.  Fogalmak
    3.2.  Tds referencia
    3.3.  Példa egy táblával
    3.4.  Példa több táblával
4.  Osztály referencia
    4.1.  Sqlconnection osztály
    4.2.  Sqlquery osztály
    4.3.  Sqlsequence osztály
    4.4.  Absztrakt tableentity osztály
    4.5.  Tableentity leszármazottak
    4.6.  Rowset osztály
    4.7.  Rowentity osztályok
    4.8.  Columnref osztály
    4.9.  Columndef osztály
    4.10.  Indexdef osztály
A.  Oracle 10g installálása Linuxra
    A.1.  Szerver installálás
    A.2.  Kliens installálás
B.  Postgres adatbázis készítése

1.  Áttekintés

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.

Fejlesztési tervek

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.

2.  Kezdőlépések

Itt arról lesz szó, hogyan vegyük birtokba az SQL2 szoftvert.

Adatbázis szoftver telepítés

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.

CCC környezet

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.

Fordítás

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 első esetben pótoljuk az installációt. A másik két esetben csinálunk egy symlinket, ami a másik néven is elérhetővé teszi az include-okat tartalmazó directoryt.

Tesztadatok

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 esetleg valami nem működik első kipróbálásra, akkor szükségesnek mutatkozhat az so, sp, import.b scriptek elolvasása. Az áttöltött adatokat szemügyre vesszük az sqlplus (Oracle), és psql (Postgres) kliens programokból is.

Példaprogramok

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:

Ez a párhuzamosság érzékeltetni próbálja, hogy ugyanabból a forrásból kétféle program készül, amik ugyanazt ugyanúgy csinálják, csak az egyik Oracle-en, a másik Potgresen. Vannak programok (programcsoportok), amikhez találunk s-* alakú indítóscriptet, más programokat közvetlenül indíthatunk.

binarydata.exe
Teszteli, hogy a memó mezők képesek-e bináris adatok tárolására.
connect.exe
Teszteli a bejelentkezést, kiírja a szerver változatszámát.
memleak.exe
Teszteli, hogy a programból szivárog-e a memória. Az Oracle kliens bizonyos változatai ebből a szempontból hibásak.
s-deadlock-detect
Demonstrálja a deadlock kezelést.
sequence0.exe
Létrehoz egy sequence objektumot.
sequence.exe
Növeli az előző sequence objektumot.
s-query-sqlquery
Lekérdezés sqlquery objektummal.
s-query-tableentity
Lekérdezés tableentity objektummal.
s-te-demo
Különféle lekérdezések és adatmódosítások tableentity objektummal.
s-tran-autolock
Demonstrálja a konkurrencia kezelést automatikus lockolással.
s-tran-manuallock
Demonstrálja a konkurrencia kezelést explicit lockolással.

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.

Jáva terminálos browse

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.

3.  Tableentityk használata

3.1.  Fogalmak

tableentity

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.)

rowentity

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.

rowset

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.

primary key

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.

columndef

A tableentity oszlopainak megadására columndef objektumokat használunk.

columnref

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.

indexdef

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.

3.2.  Tds referencia

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.

name:

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)

version:

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"

table:

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.

join:

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

column:

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.

colref:

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ó.

primkey:

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,...

index:

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.

select:

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.

3.3.  Példa egy táblával

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.

3.4.  Példa több táblával

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

4.  Osztály referencia

4.1.  Sqlconnection osztály

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:

driver
Osztályváltozó, ami a konstruktor névterét tartalmazza.
sqlcommit
Végrehajt egy commit utasítást, egyúttal automatikusan indítja a következő tranzakciót.
sqldisconnect
Megszünteti az adatbáziskapcsolatot. Az alkalmazási programnak ügyelnie kell arra, hogy a kapcsolatok ne halmozódjanak fel, mert akkor elfogynak bizonyos erőforrások (handlerek). Ezért minden feleslegessé váló kapcsolatot explicite meg kell szüntetni.
sqlexec
Végrehajt egy tetszőleges SQL utasítást. Ha az utasítás nem hajtható végre (vagy hibás), akkor futási hiba keletkezik.
sqlexecx
Végrehajt egy tetszőleges SQL utasítást. Ha az utasítás nem hajtható végre (vagy hibás), akkor visszaadja a hiba leírását tartalmazó error objektumot, egyébként NIL-t ad.
sqlisolationlevel
Beállítja a tranzakció izolációs szintet. Két paramétere van. Az első paraméter helyén az ISOL_SERIALIZABLE vagy ISOL_READ_COMMITED konstansokat lehet megadni. A második paraméter egy opcionális flag. Ha flag==.f. (ez a default), az izolációs szint csak ideiglenesen, az adott tranzakción belül lesz átállítva. Ha flag==.t., akkor a beállítás hatálya az egész session.
sqlquerynew
A con:sqlquerynew(select_statement) metódushívás gyárt egy sqlquery objektumot (lásd alább). Ezzel tetszőleges SQL lekérdezés végrehajtható.
sqlrollback
Végrehajt egy rollback utasítást, egyúttal automatikusan indítja a következő tranzakciót.
sqlsequencenew
A con:sqlsequencenew(sequence_name) metódushívás gyárt egy sqlsequence objektumot (lásd alább).
version
Megadja az adatbázisszerver verzióját (szövegesen).

4.2.  Sqlquery osztály

Az sqlquery objektumok a con:sqlqueryNew(select_stmt) objektumgyártó metódushívással keletkeznek. A metódusok:

close
Lezárja az sqlquery objektumot. Az alkalmazásoknak ügyelnie kell arra, hogy a lezáratlan sqlquery objektumok ne halmozódjanak fel, másképp elfogynak bizonyos erőforrások (handlerek). Ezért minden feleslegessé váló sqlquery objektumot explicite le kell zárni.
connection
Tárolja magában az adatbáziskapcsolatot, amivel létrehozták.
fcount
Megadja a select utasítással keletkező eredménytábla oszlopainak számát.
findcolumn
Neve alapján megmondja az oszlop indexét az oszlopok listájában.
fname
Indexe alapján megmondja az oszlop nevét.
isnull
A q:isnull(x) metódushívás megmondja, hogy az x-szel azonosított oszlop utoljára fetch-elt értéke null volt-e, x az oszlop neve vagy indexe lehet.
getchar
A q:getchar(x) metódushívás kiolvassa, és stringre konvertálva visszaadja az x-szel azonosított oszlop utoljára fetch-elt értékét, x az oszlop neve vagy indexe lehet.
getdate
A q:getdate(x) metódushívás kiolvassa, és dátumtípusra konvertálva visszaadja az x-szel azonosított oszlop utoljára fetch-elt értékét, x az oszlop neve vagy indexe lehet.
getlogical
A q:getlogical(x) metódushívás kiolvassa, és logikai típusra konvertálva visszaadja az x-szel azonosított oszlop utoljára fetch-elt értékét, x az oszlop neve vagy indexe lehet.
getnumber
A q:getnumber(x) metódushívás kiolvassa, és számtípusra konvertálva visszaadja az x-szel azonosított oszlop utoljára fetch-elt értékét, x az oszlop neve vagy indexe lehet.
next
Beolvassa (fetch) a lekérdezés következő eredménysorát. Visszatérése .t., ha volt még sor, egyébként (miután a sorok elfogytak) .f.. Az eredménytáblán egyszer, előrefelé lehet a next-ekkel végighaladni.

4.3.  Sqlsequence osztály

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:

cache
A cache attribútum default értéke 1. A seq:create metódus hatására olyan adatbázisbeli sequence keletkezik, ami egyszerre cache darabszámú értéket generál az alkalmazás számára. Ezzel a sequence használata gyorsul, viszont kihasználatlanul maradhatnak értékek.
connection
Tárolja az adatbáziskapcsolatot.
create
Létrehozza az adatbázisban a sequence objektumot. A sequence összes jellemzőjének figyelembevételével generál egy create sequence utasítást és elküldi azt a szervernek.
cycle
Ha az cycle attrubútum értéke .t., akkor a olyan adatbázisbeli sequence keletkezik (a seq:create metódus hatására), ami új kört kezd, ha eléri a minimális/maximális értékét.
drop
Megszünteti a sequence-t az adatbázisban (küld egy drop sequence utasítást a szervernek).
increment
A kreáláskor keletkező sequence növekménye. Az attribútum értékével nő (csökken) a sequence a nextval metódus végrehajtásakor, a default érték 1.
maxvalue
A sequence maximális értéke.
minvalue
A sequence minimális értéke.
name
A sequence objektum adatbázisbeli neve. Megadhatjuk az objektumgyártó függvényben, vagy utólag egyszerű értékadással. Az adatbázisban a sequence-ek egyedi névvel kell rendelkezzenek, tehát nem lehet egy sequence-nek és pl. egy adattáblának azonos neve. Ha a sequence már létezik az adatbázisban, akkor azt rögtön használhatjuk (nextval), vagy eldobhatjuk (drop). Ha még nem létezik, akkor előbb létre kell hozni (create).
nextval
Lépteti a sequence-t, egyúttal szolgáltatja az új értéket.
start
Beállítja a létrehozandó sequence kezdőértékét. Növekvő sequence-eknél a default kezdőérték minvalue. Csökkenő sequence-eknél a default kezdőérték maxvalue.

4.4.  Absztrakt tableentity osztály

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.

connection
Az adatbáziskapcsolat objektumot tartalmazza.
create
Kreál egy adatbázistáblát, aminek a struktúrája megegyezik a tableentity struktúrájával. Ugyancsak kreálja a tableentityben definiált indexeket. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van.
delete
A t:delete(e) törli a tableentity által reprezentált tábla mindazon sorait, melyekben a primary key oszlopok az e rowentity objektumból kiolvasható kulccsal egyeznek. Persze a koncepció az, hogy csak egy sor törlődjön, de ez csak akkor lesz így, ha a primary key valóban egyedi. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van. Ha ez az egyetlen elem egy view, akkor annak jellegétől függően egyes esetekben működik, más esetekben nem. Látható, hogy milyen fontos a primary key egyediségére ügyelni, máskülönben egy óvatlan update, vagy delete könnyen nem várt eredményre vezet. Az egyediséget úgy lehet biztosítani, hogy kreálunk egy unique minősítésű indexet, ami éppen a primary key oszlopaiból áll. Ekkor az adatbázisszerver megakadályozza ismétlődő kulcsok bevitelét.
drop
Megszünteti azt a fizikai adattáblát, amit a tableentity reprezentál. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van, és az egy valódi tábla (nem pedig view).
getcolumn
Az oszloplistából kikeres egy oszlopdefiníciót a neve alapján.
getprimarykey
A key:=t:getprimarykey(e) hívás kiolvassa az e rowentity objektumból az elsődleges kulcsot, és visszaad egy arrayt, ami a klucsszegmenseket tartalmazza. Ezzel a kulccsal hívható a find metódus.
insert
A t:insert(e) berak a tableentity által reprezentált adattáblába egy új sort, melyet az e rowentity objektum adataival tölt fel. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van. Ha ez az egyetlen elem egy view, akkor annak jellegétől függően egyes esetekben működik, más esetekben nem.
instance
Ad egy üresre inicializált rowentity objektumot.
show
A t:show(e) hívás kilistázza az e rowentity objektumot.
tablist
Egy array, ami a táblák (vagy view-k) felsorolását tartalmazza a következő alakban:
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.

update
A t:update(e) módosítja a tableentity által reprezentált tábla mindazon sorait, melyekben a primary key oszlopok az e rowentity objektumból kiolvasható kulccsal egyeznek. Persze a koncepció az, hogy csak egy sor módosuljon, de ez csak akkor fog teljesülni, ha a primary key valóban egyedi. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van. Ha ez az egyetlen elem egy view, akkor annak jellegétől függően egyes esetekben működik, más esetekben nem.
zap
Törli a tableentity által reprezentált adattábla minden sorát. Csak akkor alkalmazható, ha a tablistnek egyetlen eleme van, és az egy valódi tábla (nem pedig view).

4.5.  Tableentity leszármazottak

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.

__primarykey__
Osztályváltozó, egy array, ami a primary key-t alkotó oszlopokat sorolja fel. A koncepció fontos eleme, hogy a primary key egyedileg azonosítja a rowentity objektumokat. Ha valami miatt ez nem teljesül, akkor egy update, vagy delete metódus egy helyett több objektumot is módosíthat, vagy törölhet. A primary key egyediségét úgy lehet biztosítani, hogy kreálunk egy unique minősítésű indexet, ami éppen a primary key oszlopaiból áll. Ekkor az adatbázisszerver megakadályozza ismétlődő kulcsok bevitelét.
__rowclassid__
Nem módosítható osztályváltozó, a tableentityhez tartozó rowentity osztály egészszám azonosítóját tartalmazza.
__rowclassname__
Nem módosítható osztályváltozó, a tableentityhez tartozó rowentity osztály nevét tartalmazza.
__tabjoin__
Nem módosítható osztályváltozó. Ha a tableentity több táblát tartalmaz, akkor ezeket általában össze kell valahogy kapcsolni. A tabjoin attribútum tartalmazza az SQL from záradék szövegét, ami megadja az összekapcsolás módját. Például a p és q alias nevű táblák összekapcsolása a számlaszám oszlopokkal így nézhet ki:
p left join q on szamlaszam=qszamlaszam

columnlist
Osztályváltozó, az oszlopok leírását tartalmazó array. Minden oszlopot egy columndef objektum képvisel az arrayben.
find
Az e:=t:find({keyseg1,keyseg2,...}) metódushívás veszi az alaptábla azon sorát, ami a megadott kulccsal egyezik, és létrehoz vele egy új rowentity objektumot. Ha nincs sor a megadott kulccsal, akkor az eredmény NIL. A find metódus használatának másik módja e1:=t:find(e0). Itt find a kulcsot a mintaként használt e0 rowentity objektumból olvassa ki. Itt e0 és e1 egymástól függetlenül létező objektumok, nem csak egymás referenciái.

A find és select metódusokkal lehet a tábla kiválasztott sorait lockolni.

A lockolt sorokat más tranzakció nem lockolhatja, és nem módosíthatja. A lockot csak a tranzakció vége (commit/rollback) oldja fel.

Megjegyzés: Postgresben nincs timeout támogatás, ezért minden lock végtelen ideig, vagy a deadlock detektálásáig vár.

select
A tableentityknek mindig van egy automatikus select metódusa, ami az alaptábla minden sorát szolgáltatja szűrés és rendezés nélkül. Minden selectnek két opcionális paramétere van: az első a bind értékeket felsoroló array, a második a lockolást módját előíró szám (mint a find-nál). Speciálisan az automatikus selectnek sosincsenek bind változói, ezért az első paramétert sosem használjuk, és mivel ritkán célszerű egy egész táblát egyszerre lockolni, a másodikat is csak ritkán.
select*
A kulccsal meghatározott egyetlen sort szolgáltató find és az összes sort szolgáltató select metódus mindig létezik, mert a programgenerátor ezeket automatikusan elkészíti. Emellett fel lehet szerelni a tableentityt ún.  opcionális selectekkel. Az opcionális selecteket egy lehet megadni. A where záradék sorszámozott paramétereket tartalmazhat, pl.
szamlaszam like :1

Legyen 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.

version
Nem módosítható osztályváltozó, tetszőleges szöveget tartalmazhat, általában a tableentity definíció változatszámát tároljuk benne.

4.6.  Rowset osztály

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:

next
A next metódus szolgáltatja az eredménytábla következő sorával feltöltött rowentity objektumot. A nextekkel a select eredménytábla elejétől a végéig egyesével lehet haladni, semmilyen más pozícionálás nem lehetséges. Ha a sorok elfogynak, akkor a rowset automatikusan lezáródik, és a next minden további hívása rowentity helyett NIL-t ad.
close
Lezárja a rowsetet. A lezáratlanul hagyott rowsetek felhalmozódása működésképtelenné teszi a programot, mert elfogynak a handlerek, ezért az alkalmazásnak minden rowsetet le kell zárni.

4.7.  Rowentity osztályok

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:

Az első esetben üresre inicializált objektumot, a másik két esetben az adatbázisból betöltött (fetch) objektumot kapunk.

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:

insert
Az e:insert metódushívás új sort tesz a tableentity által reprezentált táblába a rowentity objektum adataival feltöltve. Csak azok az oszlopok jelennek meg a kapcsolódó SQL insert utasításban, amiknek a program értéket adott, a többi oszlop null értéken marad. Ez a metódushívás csak alternatív formája a tableentitynél látott t:insert(e) alaknak, ahol t az e-hez tartozó tableentity.
update
Az e:update metódushívás módosítja a tableentity által reprezentált tábla azon sorát, amit az e-ből kiolvasható primary key azonosít. Csak azok az oszlopok jelennek meg az kapcsolódó SQL update utasításban, amiket a program megváltoztatott. Ez a metódushívás csak alternatív formája a tableentitynél látott t:update(e) alaknak, ahol t az e-hez tartozó tableentity.
delete
Az e:delete metódushívás törli a tableentity által reprezentált tábla azon sorát, amit az e-ből kiolvasható primary key azonosít. Ez a metódushívás csak alternatív formája a tableentitynél látott t:delete(e) alaknak, ahol t az e-hez tartozó tableentity.
find
Az e1:=e0:find metódushívás behozza azt az objektumot, amit a mintaként használt e0-ból kiolvasható primary key azonosít. Ugyanaz, mint az e1:=t:find(e0) alak.
show
Az e:show metódushívás kilistázza az objektumot, ugyanaz, mint a t:show(e) alak, ahol t az e-hez tartozó tableentity.

4.8.  Columnref osztály

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.

name
A oszlop (mező) programbeli neve.
tabid
A tábla hivatkozási neve (alias), amiben az oszlop van.
colid
Az oszlop adatbázisbeli neve.
qualified
Metódus, ami az oszlop minősített nevét adja.

4.9.  Columndef osztály

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:

type
Az oszlop Clipper típusát azonosító string: Cw, Nw[.d], D, L, M. A memókat az új M típus jelöli. A típust, szélességet, decimálisok számát egybe kell írni, pl. "N16.2".

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.

notnull
Egy flag, ami mutatja, hogy kötelező-e értéket adni az oszlopnak. A feltételt az adatbázisszerver ellenőrzi az insert/update utasítások végrehajtásakor.
offset
A mező kezdete a rowentity adatbufferében.
size
A mező mérete a rowentity adatbufferében.
block
Kódblock, ami kiolvassa/beírja az adatot a bufferből/bufferbe.
sqltype
Meghatározza a mező SQL típusát. Ha nemlétező táblát kreálunk a tableentityvel, akkor (és csak akkor) kap szerepet az sqltype metódus, ami SQL típust rendel az oszlop Clipper típusához.

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.

indvar
Indikátor változó.
isnull
Mutatja, ha a beolvasott mező eredeti értéke null. A rowentityk oszlopmetódusai a null értéket nem NIL-re képezik, hanem egy egyező típusú empty értéket adnak.
isdirty
Mutatja, hogy a program módosította-e a mezőt.
eval
A col:eval(rowentity[,x]) metódushívás kiolvassa az oszlop értékét a rowentityből, vagy x-et értékül adja az oszlopnak. Alkalmazási programban inkább ezzel a formával találkozunk: rowentity:colname[:=x].

4.10.  Indexdef osztály

Index adatok tárolására használjuk az indexdef objektumokat.

name
Az Oracle-beli név kiegészítője. A tableentity:create metódus a tábla kreálásakor a tableentityben tárolt indexeket is létrehozza. Az indexek adatbázisbeli neve tabname+'_'+i:name lesz, ahol tabname a tableentity:tablist első eleméből van.
seglist
Az indexet alkotó oszlopok columndef-jeinek listája.
unique
Logikai érték, mutatja, hogy egyedi-e az index.

A.  Oracle 10g installálása Linuxra

A.1.  Szerver installálás

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.

Kezdeti állapot

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.

Csoportok, felhasználók

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.

Oracle letöltés

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

Base directory

Létrehozzuk az Oracle base directoryt:

    mkdir /opt/oracle/base

/etc/profile

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

/etc/sysctl.conf

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

/etc/security/limits.conf

Beírjuk /etc/security/limits.conf-ba:

    *               soft    nproc   2047
    *               hard    nproc   16384
    *               soft    nofile  1024
    *               hard    nofile  65536

/etc/pam.d/login

Beírjuk /etc/pam.d/login-ba:

    session    required     /lib/security/pam_limits.so

Symlinks

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

RedHat-et emuláció

Létrehozzuk a /etc/redhat-release filét a következő tartalommal:

    Red Hat Linux release 2.1 (drupal)

runInstaller

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).

/etc/oratab

Beírjuk /etc/oratab-ba:

    test:/opt/oracle/base/product/10g:Y

/etc/init.d/oracle

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

Hálózat

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.

Tablespace, konto schema

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.

A.2.  Kliens installálás

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.

B.  Postgres adatbázis készítése

Postgres változatok

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.

Új adatbázis cluster készítése

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` 

Indító/leállító scriptek

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

Új adatbázis készítése

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.

Konto schema

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.


Jegyzetek:

1ComFirm BT.