ugrás a tartalomhoz

Perl alapjai IX. - Adatbáziskezelés

Bártházi András · 2005. Május. 8. (V), 21.00
Perl alapjai IX. - Adatbáziskezelés
Egy modern, általános célú programozási nyelv elengedhetetlen része manapság az adatbáziskezelés megvalósítása. A Perl ezt már régóta tudja, sőt, egy egységes réteget nyújt, mely segítségével (ahogyan ez a többi ilyen réteg esetén is van) többé-kevésbé adatbázis függetlenül tudunk dolgozni. Mai cikkünkben a Perl adatbáziskezelési lehetőségeit (konkrétan a DBI modul által nyújtott fontosabb szolgáltatásokat) fogjuk áttekinteni.

A Perl adatbáziskezeléséne a magját képező modult DBI-nak (Database Interface-nek) hívják, az egyes adatbázisokhoz pedig különböző DBD (Database Driver) modulok segítségével kapcsolódhatunk. Ezeknek a moduloknak a célja egy alacsony szintű egységes interfész biztosítása a különböző gyártó specifikus adatbázis kapcsolati lehetőségekhez. A DBI modul azonban nem csak a klasszikus adatbázisokhoz történő kapcsolat lehetőségét nyújtja, léteznek olyan DBD modulok is, melyek adatbázis jellegű megoldásokhoz kínálnak hozzáférést - például ilyen a DBD::CSV modul, mely segítségével SQL nyelven férhetünk hozzá CSV fájlokhoz.

Léteznek más lehetőségek is adatbáziskezelésre, melyek különböző rétegek nélkül, "közvetlen" interfészt biztosítanak az adatbázisokhoz, azonban ezek egy részét már nem is fejlesztik, vagy ha igen, akkor sem javasolt a használatuk, kivéve, ha nincsen más lehetőség. Az egységes DBI interfész használatával ugyanis megkönnyíthetjük a dolgunkat egy esetleges adatbázis átálláskor (persze az egyes adatbázisspecifikus lekérdezéseket nem fogja átírni helyettünk).

A DBI lehetőségei

A DBI a CPAN-ról letölthető legrégebbi 1.13-as változata 1999. július 11-i, ez talán jelzi, hogy ez a modul egyike a legrégebbi Perl moduloknak, igencsak hosszú múltra tekinthet vissza. A jelenleg legfrissebb változata az 1.48-as, melyet 2005. március 14-n adott ki a szerző/karbantartó, Tim Bunce.

A DBI az alapszintű funkciókhoz kínál egységes objektum orientált hozzáférési interfészt. Segítségével kapcsolódhatunk az adatbázishoz, illetve megszakíthatjuk a felépített kapcsolatot, lekérdezéseket készíthetünk elő, s indíthatunk el, majd az eredményeket kérdezhetjük le különböző módokon. Ezen kívül lehetőséget biztosít még a tranzakciókezelésre, s a hibakezelésre is. Ezek azok a lehetőségek, melyeket minden adatbáziskezelő DBD modulnak meg kell valósítania, s melyeknél általában nincs is szükség többre. Ha ennél több funkcióra vágyunk, vagy magasabb szintű absztrakcióra, azt magunknak kell megvalósítanunk.

Alapok: connect és disconnect

A DBI modul használatához mindenekelőtt jeleznünk kell a Perl fele, hogy használni szeretnénk. Ezt a következő módon tehetjük meg:
use DBI;
Ezután kapcsolódni kell az adatbázishoz, melyet a connect metódus segítségével tehetünk meg:
my $dbh = DBI->connect($dsn, $felhasznalo, $jelszo, { ...parameterek... });
A DBI modultól, illetve még a Perl nyelvtől is teljesen függetlenül, általánosan elmondható, hogy egy adatbázishoz történő kapcsolódás "drága" mulatság lehet, azaz tizedmásodpercek telhetnek el, míg a kapcsolat létrejön. Ez általában adatbázismotortól függ, van, ahol ez pillanatok alatt megvan, s van, ahol jelentős szempont lehet programunk tervezésekor. Ami biztos, hogy a kapcsolódásnak van egy időtartama, s a legoptimálisabb módon akkor járunk el, ha az adatbázishoz a programunk futása során csak egyszer csatlakozunk (sőt, ezt is csak akkor tesszük meg, ha tényleg szeretnénk az adatbázishoz fordulni a futás során), s a programunk végén pedig bontjuk az így kiépített kapcsolatot.

A connect metódus három állandó paramétert vár tőlünk, s további opcionálisakat is megadhatunk. Az első paraméter, a DSN (Data Source Name) értéket adja meg. Ez nem tartalmaz mást, mint az adatbázismotor típusát, majd pedig a konkrét adatbázis elérhetőségét. A második és haramdik paraméterek az adatbázishoz történő kapcsolódáshoz szükséges felhasználót és jelszót adják meg (ez olyan adatbázisoknál, ahol nincs szükség azonosításra, üresen hagyható). A további paraméterek általában adatbázismotor, illetve DBD megvalósítás függőek, ezekről a konkrét DBD dokumentációjában találhatunk információkat.

A DSN felépítésére nincs előírt, kötelező mód, azonban a következőképpen kell kezdőnie:
dbi:Meghajtónév:
Pár tipikus DSN érték:
dbi:Pg:dbname=adatbazis
dbi:Pg:dbname=adatbazis;host=dbszerver
dbi:mysql:adatbazis:localhost
dbi:Oracle:sema
dbi:CSV:f_dir=/home/boogie/cvskonyvtar
A lehetséges különbségek abból adódnak, hogy az adatbázismotorok másképp működnek a hozzáférés tekintetében. Míg a MySQL adatbázisokat definiál, s ezekhez rendel adatbázis táblákat, az Oracle sémákon keresztül teszi elérhetővé a különböző táblákat, a DBD::CSV modulnak pedig egy könyvtárat kell megadni, s az egyet "táblák" az ebben levő fájlok lesznek.

A connect metódus visszatérési értéke egy objektum (database handler), ennek a metódusaival tudjuk végrehajtani az egyes parancsokat. Mielőtt ezekkel megismerkednénk, nézzük meg, hogyan lehet lezárni a kapcsolatot:
$dbh -> disconnect();
Mint látható, nem túl bonyolult, meghívtuk a disconnect metódust.

A DBI modul segíségével egyszerre több kapcsolatot is kiépíthetünk, akár ugyanazon adatbázishoz, akár különbözőkhöz, a visszakapott objektumok kezeléséről kell csak gondoskodnunk.

Parancsok: do

A legegyszerűbb módon a do metódus segítségével hajthatunk végre SQL parancsokat. Találati listát nem ad vissza, ezért SELECT jellegű parancs kiadásának nincs értelme segítségével. Ismételten végrehajtandó, változó paraméterű parancsokhoz sem ajánlott a használata, mivel előkészítést nem tesz lehetővé. Használata:
my $rows = $dbh -> do("DELETE * FROM adattabla");
Második paraméterként egy hash elemként további paramétereket adhatunk át, ennek feldolgozása a konkrét DBD motorra van bízva.

Amennyiben az SQL kifejezésünkben változót szeretnénk használni, ez a metódus arra is biztosít lehetőséget. Fontos, hogy semmiképp se az idézőjelen belülre, magába a parancsba írjuk ezt a változót, hanem helyette egy kérdőjelet használjunk, s a változókat rendre a kérdőjelek sorrendjében írjuk le a harmadik paramétertől kezdve. Egy példa:
my $rows = $dbh -> do("DELETE * FROM adattabla WHERE id=?", undef, $id);
A második paraméternek undef értéket adtunk, mivel nincsenek extra paramétereink, illetve átadtuk az azonosítót, melyet a kérdőjel helyére fog behelyettesíteni az adatbázis szerver. További változók esetén negyedik, ötödik paraméternek írhattuk volna a változóneveket. A kérdőjel előtt, mögött nem kell sem idézőjelet, sem aposztrófot használni. Azért célszerűbb ezt a megoldást használni, mert így tartalmazhat bármely értéket a változó, nem lesz gondunk belőle.

A do metódus egy darab SQL utasítást tartalmazhat, többet nem (ha pontosvesszővel elválasztjuk, akkor sem). Ez alól kivétel pár DBD megvalósítás, de jobban járunk, ha nem használjuk ki ezt a lehetőségüket.

A do visszatérési értéke egy szám, mely az utasítás által érintett adatbázistábla sorok számát tartalmazza. Ha nem tudni ezt az értéket, a motor nem támogatja ennek visszaadását, vagy egyéb ok miatt nem áll rendelkezésünkre ez az érték, akkor -1-et kapunk. Ha hiba történt, a visszatérési érték undef lesz.

Parancsok: prepare és execute

A do parancs egy lépésben tartalmazott két lépést, melyet a modern adatbáziskezelők lehetővé tesznek: az előkészítést és a végrehajtást. Az előkészítés (prepare) lényege, hogy az általunk lefuttatni kívánt parancsot átadjuk neki, amit értelmez, s előkészít a futtatásra. Az SQL utasítás egy sztring, melyet nem egy az egyben futtat le, hanem először meg kell értenie, elemeznie kell, hogy hogyan lesz a leghatékonyabb futtatni, stb. A végrehajtás ezután egy így már előkészített parancsot tud végrehajtani. Ennek a két lépésnek a szétválasztására akkor van szükségünk, ha sok ugyanolyan szerkezetű, csak változó paraméterekben különböző parancsot szeretnénk végrehajtani. Ekkor egyszer előkészítjük, majd pedig többször végrehajtjuk. Ha csak egy végrehajtás van, a szétválasztásnak nincsen előnye a két lépéses megoldáshoz képest, használhatjuk a do-t is.

Egy példa több elem beszúrására:

my @data = ('egy', 'kettő', 'három', 'négy');
my $sth = $dbh -> prepare("INSERT INTO adattabla (value) VALUES (?)");
foreach (@data) {
  $sth -> execute( $_ );
}
A prepare második paramétereként itt is adhatunk át egy hash referenciát, ha arra szükségünk lenne.

Mint ahogy a kódból látható, itt a kérdőjelek az előkészítésnél jelennek meg, a konkrét behelyettesítés pedig a végrehajtásnál történik meg. Figyeljünk rá, hogy az execute metódust nem az adatbázis kezelőnk, hanem a prepare által visszaadott előkészített parancs objektumon futtassuk le!

Az execute visszatérési értéke a do-hoz hasonlóan az érintett sorok száma lesz.

Lekérdezések: fetchrow_*

Egy lekérdezés végrehajtása után (az execute futtatásával) az eredményt sorok formájában olvashatjuk vissza, egymás után. Két lehetőségünk van, vagy visszakérni az összes eredményt egy változóba, vagy pedig soronként visszatérve feldolgozni azt. Általában ez utóbbit szoktuk alkalmazni, így ezt tekintjük át elsőként.

A fetchrow_* utasításokból három van. Az első egy tömböt ad vissza, a második egy tömb referenciát, a harmadik pedig egy hash referenciát. Mind a három metódus a már lefuttatott parancs objektumon hajtható végre, s mind a három esetben egy-egy sor értékeit kapjuk vissza eltérő adatszerkezetben.

A fetchrow_array egy tömböt ad vissza, az adott sor oszlopainak értékeivel (ahogy az a lekérdezésben szerepelt). Használata rendkívül egyszerű:

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
while ( my @array = $sth -> fetchrow_array() ) {
  print "id => $array[0] -- value => $array[1]\n";
}
A while ciklusban azt használtuk ki, hogy a függvény visszatérési értéke undef lesz, ha az összes sort lekérdeztük már. Addig pedig egy tömbbe tesszük a visszakapott sorokat, s a ciklusban kiírjuk a tartalmát.

Mivel az SQL oszlopok bármely értéke tárolható egy Perl skalár változóban, ezért minden további konverzió nélkül kapjuk vissza az egyes értékeket. Egy szám vagy sztring értéket egy az egyben, egy dátumot sztringként kaphatunk meg. Ha egy oszlop értéke NULL, akkor undef értéket fogunk kapni.

A lekérdezés hátránya lehet, hogy ha valamiért megváltoztatjuk a lekérdezésben az oszlopok sorrendjét, akkor át kell írnunk a tömb indexeket. Ha pedig egy általános SELECT * formát használunk, nem tudhatjuk biztosan sohasem, hogy melyik oszlop a tömbben hányadik helyet fogja elfoglalni. Ezzel együtt ez az egyik leggyorsabb lekérdezési mód.

A fetchrow_arrayref gyakorlatilag a fetchrow_array mögött álló kód. Egy tömbre ad vissza referenciát, melyet aztán az előbbihez hasonló módon használhatunk:

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
while ( my $arrayref = $sth -> fetchrow_arrayref() ) {
  print "id => ${$arrayref}[0] -- value => ${$arrayref}[1]\n";
}
Nem árt tudni, hogy minden lekérdezéskor ugyanarra a tömbre fog mutatni a referencia, így például a referencia eltárolva, majd lekérdezve egy későbbi sorban nem az eltároláskori sorhoz, hanem mindig az aktuálishoz férünk hozzá. Ha mégis egy korábbi sor kell, másoljuk le a teljes tömböt, vagy csak azokat az értékeket, melyre később is szükségünk lesz.

A fetchrow_hashref egy a korábbiaknál kicsit kényelmesebben használható megoldást nyújt egy sor lekérdezésére. Egy hash-t ad vissza, melyben oszlopnév, oszlopérték párok szerepelnek. Használata:

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
while ( my $hashref = $sth -> fetchrow_hashref() ) {
  print "id => ${$arrayref}{id} -- value => ${$hashref}{value}\n";
}
Az adott oszlop értékére a nevével tudunk hivatkozni, mely a feldolgozást talán egy kicsit átláthatóbbá teszik.

Lekérdezések: bind_columns

Mielőtt rátérnénk a teljes eredménylista egyszerre történő lekérdezésére, egy másik lehetőséget is áttekintünk. A bind_columns segítségével a visszakapott oszlopokat előre megadott változókhoz rendelhetjük, ezzel talán még a hash referenciás megoldásnál is átláthatóbbá téve kódunkat. A használata a következő:

$sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
my $id, $value;
$sth -> bind_columns(\$id, \$value);
while ( $sth -> fetch() ) {
  print "id => $id -- value => $value\n";
}

Lekérdezések: fetchall_*

A fetchall_* metódusok hasonlóak ugyan a fetchrow_* nevű társaikhoz, de más célt szolgálnak.

A fetchall_arrayref-nek két paramétere lehet. Az elsővel a visszakapott oszlopokat lehet szűrni, a második pedig a lekért sorok maximális számát tartalmazza. Az oszlopok szűrése kétféleképpen történhet: meg lehet adni egy tömb hivatkozást, vagy egy hash hivatkozást. Mind a kettőre egy-egy példa:

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
my $arraysref = $sth -> fetchall_arrayref( [1] );
for ( @{$arraysref} ) {
  print "value => ${$_}[0]\n";
}
Ebben az esetben egy tömbhivatkozást adtunk át, melyben az 1 érték szerepelt (több értéket is adhattunk volna). Az itt megadott sorszámú oszlopokat fogja csak bennehagyni a metódus a visszatérő tömbben, azaz ebben az esetben a második, azaz a value oszlopot.

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla ORDER BY id");
$sth -> execute();
my $arraysref = $sth -> fetchall_arrayref( { value => 1} );
for ( @{$arraysref} ) {
  print "value => ${$_}[0]\n";
}
Most egy hashhivatkozást adtunk át, melyben a value => 1 pár szerepelt. A lekérdezés során az eredményben azok az oszlopok fognak bennemaradni, melyeknél a pár nevében az oszlop szerpel, s a pár értéke 1.

A fetchall_hashref-nek kötelezően egy paramétere lehet. Azt az oszlopot kell megadni, mely a lekérdezés kulcsául szolgálhat. A végeredmény egy olyan hash lesz, melynek elemei a visszakapott sorok: a kulcs a megadott kulcsként használható oszlop, az érték pedig a sor hash-e lesz. Ez egy elég kényelmesen használható adatszerkezetet ad vissza, mint azt a következő példából is láthatjuk:

my $sth = $dbh -> prepare("SELECT id, value FROM adattabla");
$sth -> execute();
my $hashesref = $sth -> fetchall_hashref( 'id' );
print "A 36-os játékos: $hashesref->{36}->{value}\n";
A hash kulcsai tehát így az id-k lettek, azok tartalma pedig egy olyan hash, melynek kulcsai az oszlopok nevei, értékei pedig az oszlopok értékei.

Ha egy tábla kulcsa több oszlopból áll, a fetchall_hashref azt is le tudja kezelni. Paraméteréül egy tömb referenciát kell átadni, melynek elemei a tábla kulcsoszlopai:

my $sth = $dbh -> prepare("SELECT id1, id2, value FROM adattabla");
$sth -> execute();
my $hashesref = $sth -> fetchall_hashref( ['id1', 'id2'] );
print "A 3/6-os játékos: $hashesref->{3}->{6}->{value}\n";

Lezárás: finish

Egy lekérdezés automatikusan lezárásra kerül, s felszabadul a hozzá rendelt memória, ha az eredményének minden sora kiolvasásra került. Ha ez nem történik meg, akkor a memóriában marad, s foglalja a helyet, melyet a legjobb ha elkerülünk, s magunk zárjuk le a lekérdezést. Ehhez a lekérdezést reprezentáló kezelő objektum finish metódusát kell meghívni:
$sth -> finish();

További lehetőségek

Maga a DBI is kínál a bemutatottaknál több lehetőséget, illetve az egyes adatbázis motorokhoz írt DBD illesztők is nyújtanak továbbiakat. Ezeket legfőképpen a DBI dokumentációból ismerhetjük meg, vagy pedig a konkrét DBD kezelő dokumentációjához fordulhatunk, melyet a CPAN DBD:: listájában találhatunk meg.

A cikkben nem esett szó a konkrét DBD-kről, mivel ezekből elég annyit ismernünk, hogyan kell kapcsolódni a connect segítségével, mivel általában közvetlenül se példányosítani, se meghívni nem kell ezek egyikét se.

Összefoglalás

A mai cikk egy általános bevezetőt nyújtott a Perl adatbáziskezelési lehetőségeibe, ezzel talán nem csak a Perl programozóknak nyújtva érdekességeket. A DBI nem minden lehetősége széles körűen ismert és használt, bízom benne, hogy sikerült újat mutatni néhány régi motoros Perl programozónak is.

A következő alkalommal a tervek szerint arról lesz szó, hogy az eddigi cikkekben már hivatkozott és használt Perl modulok pontosan micsodák, hogyan használhatóak, telepíthetőek.
 
Bártházi András arcképe
Bártházi András
Az Emarsys-nál dolgozik vezető fejlesztőként, és az API-ért, integrációkért felelős termékmenedzserként. Szeret profi csapatban profit alkotni.
1

Hello! Látom már itt van

tiny · 2005. Május. 11. (Sze), 17.47
Hello! Látom már itt van jóideje ez a cikk, gondoltam megtöröm a csendet. Az igazság az, hogy én egyenlőre az első részt kezdtem olvasni, az érdekes. Amint eljutok ehhez a IX. részhez, majd írok valami érdemlegeset is. Egyébként ahogy így végignézegettem a forrásokat, teccik a dolog. Lesz PERL 6 alapjai sorozat :) ? Üdv:
Mr.Tiny
2

Perl 6 alapjai

Bártházi András · 2005. Május. 11. (Sze), 19.42
Szeretnék írni róla, de nem tudom még, hogy lesz-e rá időm. Meglátjuk.

-boogie-
6

Perl ppm

Barta · 2006. Már. 15. (Sze), 09.39
Érdekes, sok cikket találtam a perl adatrbáziskezelésről, de a ppm használatáról csak hiányos leírásokat szedtem össze innen-onnan, inkább cikkeket. Pedig anélkül se DBI, se DBD. Lesz-e erről részletesebb cikk? Üdvözlettel:
Barta Sándor
3

lemaradt..::

Őry Máté · 2005. Júl. 2. (Szo), 00.46
[series-info:right]
4

valóban

Hojtsy Gábor · 2005. Júl. 2. (Szo), 10.43
Kösz, javítva.
5

teljesen jo

adamkof · 2005. Dec. 5. (H), 10.26
Hali
Tudom a szokásos ömlengös köszönéseket mellőzzük, de nekem most kellett perl-ben írnom valamit, életemben azelőtt nem láttam perlt, és ez a cikk sorozat marha sokat segített. Köszi. Viszont megtetszett maga a nyelv is :-)
Na üdv, csak egy kis szószaporítás volt.