- předchozí článek - následující článek - obsah - úvodní stránka -

Linuxové noviny 11/99

DBI - Perlové rozhraní pro přístup k databázím

Jan Pazdziora, adelton@fi.muni.cz

Perl je jazyk pro zpracování (primárně) textů a je oblíbeným skriptovacím nástrojem WWW serverů, relační databázové servery jsou efektivním systémy pro uložení velkého množství dat a rychlý přístup k nim. Spojení Perlovské flexibility s databázovými možnostmi uchování velkých objemů údajů je pak častým úkolem i přirozeným řešením. Pro přístup k databázím slouží v Perlu DBI - DataBase Interface.

Typická relační databáze umí z uživatelského hlediska relativně málo věcí - provést SQL příkaz s nějakými parametry (INSERT, DELETE) či provést SQL dotaz a vrátit posloupnost záznamů (SELECT). DBI definuje jednotný způsob, jak databázi předat příkaz a jak dostat zpět výsledek. Protože představy různých databází o tom, jak se s nimi má pracovat, se velmi liší, je DBI navrženo modulárně. Základní DBI definuje pouze metody směrem k Perlovskému uživateli, pro konkrétní databázový server pak potřebujeme specifický DBD - DataBase Driver. Pro každý typ databáze jiný driver. Výhodou takovéhoto modulárního rozdělení úkolů je velká nezávislost na straně Perlu, na druhé straně možnost implementovat práci s databází co nejefektivněji s využitím databázi na míru šitého driveru.

Instalace DBI i jednotlivých driverů probíhá standardně:

    $ perl -MCPAN -e shell
    cpan> install DBI
    cpan> install DBD::mysql

či stažení, rozbalení a pak

    perl Makefile.PL && make &&\
        make test && make install

Tím prvním příkladem naznačuji, že DBI i drivery jsou k dispozici na CPANu, tím druhým, že jsou to standardní moduly. Co už není tak standardní je nutnost číst při instalaci dokumentaci. Databázové drivery (DBD) totiž implementují rozhraní s databází a velmi často je nutno kompilovat driver s pomocí originálních databázových hlavičkových souborů a linkovat s originálními knihovnami, nezřídka za pomoci rozšířeného vývojového prostředí. Jinými slovy, před tím, než začnete driver kompilovat a instalovat, je dobré se přesvědčit, že máte opravdu zakoupeno/nainstalováno vše, co je potřeba. (Rostoucímu množství svých kolegů, kteří na svých systémech nemají překladač, ale přesto DBI chtějí, doporučujte použití ppm - ušetříte hodně času jak jim, tak sobě.)

Předpokládáme-li tedy, že jsme úspěšně nainstalovali jak DBI, tak DBD ovladač příslušný naší databázi, následuje první interakce s databázovým serverem - connect. Nejdříve je potřeba se k serveru připojit:

    use DBI;
    my $dbh = DBI->connect('dbi:mysql:test',\
        'user', 'pass',
        { RaiseError => 1, AutoCommit => 1 });

Zde ukazujeme, že pro připojení je nutno vědět, kam se vlastně připojujeme, a dále přihlašovací jméno do databáze a heslo. Za dbi: v prvním parametru connectu je jméno driveru, například mysql, Pg, Oracle, Informix. Za další dvojtečkou pak následuje bližší specifikace -- typicky jméno databáze či databázové instance. Protože ale použité principy se server od serveru liší, je syntaxe této části přihlašovacího řetězce závislá na daném driveru - například prod, dbname=template1, host=www2:post=5553:db=test. Výsledkem connectu je tzv. databázový handler, reference na objekt, který ví, ke které databázi je připojen a jehož prostřednictvím můžeme serveru poslat SQL příkaz. Například

    $dbh->do('delete from tabulka\
       where id = 123');

Většina databázových serverů podporuje (anebo je drivery emulují) bindované parametry:

    $dbh->do('delete from tabulka\
       where id = ?', {}, $id);

Zde nespecifikujeme hodnotu přímo v SQL řetězci, ale předáváme ji jako parametr, běžný Perlovský skalár. Driver zajistí zpracování této hodnoty do podoby, kterou databáze je schopna zpracovat (případně provede expanci hodnoty do SQL příkazu, pokud databázový server bindované parametry sám o sobě nezná).

Toto jednorázové provedení příkazu je ve skutečnosti spojení několika akcí, které můžeme volat postupně:

    my $sth = $dbh->prepare('insert into tab\
       values (?, ?, ?)');
    while (<>) {
        chomp;
        my ($id, $login, $forward) = split /:/;
        $sth->execute($id, $login, $forward));
        }

Pomocí metody prepare (nad patřičným otevřeným database handlerem) nejprve připravíme tzv. statement handler s daným SQL příkazem, a pak opakovaně voláme provedení tohoto příkazu, pokaždé s jinými parametry, kdy hodnoty Perlovských skalárů jsou "dosazeny" na místa otazníků v SQL příkazu. Server tak SQL příkaz parsuje pouze jednou a při hromadném dávkovém zpracování ušetříme hodně času.

Jelikož jsme databázový handler otevřeli s atributem AutoCommit, je každý příkaz následován implicitním commitem. Pokud bychom atributem metody connect či přímo pomocí

    $dbh->{'AutoCommit'} = 0;

autocommit mód vypnuli, provedeme pak explicitní ukončení transakce voláním

    $dbh->commit;

(či $dbh->rollback). Podobně jako při předání SQL příkazu k provedení či zpracování bindovaných parametrů je věcí databázového driveru, aby Perlovské příkazy zaimplementoval tak, aby vyústily ve správnou akci databázového serveru.

Statement handler je možnou využít nejen k násobnému provedení jednoho SQL příkazu, ale také pro získání výsledků příkazu SELECT:

    my $sth = $dbh->prepare('select name, age
                from people where dept = ?
                    and salary > ?');
    $sth->execute($oddeleni, $plat);
    while (my ($jmeno, $vek) =\
        $sth->fetchrow_array) {
        $lide{$jmeno} = $vek;
        }

Zde je po provedení příkazu (execute) statement handler nachystaný na volání metod fetchrow_*, kterými postupně načteme jednotlivé záznamy. Opět, DBI ve spolupráci s konkrétním DBD zajistí, že hodnoty získané SELECTem jsou převedeny na rozumné Perlovské skaláry.

DBI poskytuje hodně metod a dokumentaci je rozhodně dobré přečíst - jednotlivé záznamy můžeme načíst jako hash indexovaný jmény sloupců, můžeme načíst celý seznam jedinou metodou, pomocí atributů jsme schopni vyladit chování daného spojení či říci, jak se má pracovat s položkami typu BLOB či CLOB, zda mají chyby vést přímo na die (RaiseError) či zda chceme, aby byla chybová hlášení pouze tištěna na standardní chybový výstup (PrintError) či chyby úplně ignorovány s tím, že budeme ošetřovat návratové hodnoty jednotlivých metod.

Většina toho je ale již pouze variací na zde předvedené téma: nedříve je potřeba provést connect do databáze, pak nad vytvořeným databázovým handlerem voláme metody, kterými zadáme SQL příkaz, případné parametry (buď po jednom pomocí bind_param, nebo jak jsme již viděli přímo jako další atributy do či execute), příkaz provedeme, a pokud je co číst, načteme (fetch*) výsledky.

Krása DBI spočívá v tom, že unifikuje metody napříč různými databázovými servery, tedy nemusíme si pamatovat, jaké parametry má mysql_connect ve srování s OCI8 - od toho nás odstíní patřičný databázový driver. Bindování parametrů pak nejen zvyšuje výkon odstraněním opakovaných nepotřebných volání, ale i pohodlí a bezpečí při programování, protože nad každou hodnotou převzatou z CGI formuláře nemusíme provádět ošetřování nepohodlých znaků, které se navíc opět server od serveru liší.

Relativně unikátním je databázový driver DBD::Proxy, který dovoluje vzdáleně přistupovat i k databázovým serverům, které jinak vzdálené připojení neposkytují, či ke kterým nemáme síťový modul zakoupený nebo kde klientské knihovny pro naši platformu neexistují. Pomocí proměnné prostředí DBI_AUTOPROXY či specifikací řetězce ve volání connect řekneme, ke kterému stroji a na jaký port se má náš skript připojit. Na vzdáleném počítači musí běžet DBI::ProxyServer, který spojení přijme a provede connect do lokální databáze. Všechny metody, které pak nad lokálním databázovým handlerem budeme volat, bude driver přeposílat na tento vzdálený ProxyServer a ten je předá databázovému serveru; zpět pak samozřejmě tečou výsledky.

Pokud programujete v Perlu, je DBI tím nástrojem, kterým přistupovat k databázím. Pokud potřebujete přistupovat k databázím a využít přitom výhodu skriptovacího jazyka s unifikovaným databázovým rozhraním (DBI je starší než specifikace ODBC), je Perl s DBI kandidátem, který je dobré brát v úvahu.

    $dbh->disconnect;
    __END__
*


- předchozí článek - následující článek - obsah - úvodní stránka -