Sunteți pe pagina 1din 30

300 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL despre modul de generare a listei membrilor pentru

programul banchetului anual al Ligii istorice, precum i despre modul de generare a catalogului tiprit al Ligii. Totui, DBI furnizeaz multe alte caracteristici utile. Seciunile urmtoare trateaz despre unele din acestea ntr-un mod mai detaliat, astfel nct s putei utiliza scripturile Perl nu numai pentru rularea unor simple instruciuni SELECT. Tratarea erorilor Scriptul dump_members a activat atributul de tratare a erorilor RaiseError atunci cnd a invocat metoda connect (), astfel nct erorile s cauzeze terminarea imediat a execuiei scriptului cu un mesaj de eroare. Exist i alte metode de tratare a erorilor. De exemplu, putei verifica personal apariia erorilor n loc de a cere programului DBI s execute aceast operaie. Pentru a vedea cum se poate controla modul de tratare a erorilor n DBI, s examinm mai ndeaproape argumentul final al funciei connect(). Cele dou atribute relevante sunt RaiseError i PrintError: Dac RaiseError este activat (adic primete o valoare diferit de zero), DBI apeleaz funcia die() pentru a tipri un mesaj i pentru a-i termina execuia dac se produce vreo eroare ntr-o metod DBI. Dac PrintError este activat, DBI apeleaz funcia warn() pentru a afia un mesaj atunci cnd se produce o eroare DBI, dar scriptul i continu execuia. n mod prestabilit, RaiseError este dezactivat i PrintError este activat, n acest caz, dac apelul la funcia connect () eueaz, DBI afieaz un mesaj, dar i continu execuia. Astfel, folosind metoda prestabilit de tratare a erorilor, pe care o obinei dac omitei al patrulea argument al funciei connect (), putei cuta erori n acest mod: $dbh = DBI->connect ($dsn, $user_name, Spassword) or exit (1); Dac se produce vreo eroare, funcia connect () returneaz undef pentru a indica eecul, ceea ce declaneaz apelul la exit(). Nu trebuie s afiai un mesaj de eroare, deoarece DBI va fi afiat deja unul. Dac specificai n mod explicit valorile prestabilite ale atributelor operaiei de verificare a erorilor, apelul la funcia connect () se prezint astfel: $dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => O, PrintError => 1}) or exit (1); Este ceva mai mult de scris, dar un cititor ocazional va sesiza mai rapid care este metoda de tratare a erorilor utilizat. Dac dorii s cutai personal erorile si s v afiai propriile mesaje, dezactivai att RaiseError, ct si PrintError: $dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => O, PrintError => 0}) or die "Nu se poate conecta la server: $DBI::err ($DBI::errstr)\n"; Vi' >' *,-" - ' ' fr .**.'1v-*."'r Capitolul 7 Interfaa API pentru Perl DBI 301 Variabilele $DBI : : err i $DBI : : errstr, folosite n apelul la funcia die ( ) prezentat anterior, sunt utile pentru construcia mesajelor de eroare. Aceste variabile conin codul de eroare i irul de eroare, asemntor cu funciile mysql_errno{), respectiv mysql_error ( ) din interfaa API pentru C. Dac nu dorii dect ca DBI s trateze erorile n locul dumneavoastr, astfel nct s nu fie necesar s le verificai personal, activai RaiseError: $dbh = DBI->connect ($dsn, $user_name, Spassword, { RaiseError => 1 }); Aceasta reprezint de departe metoda cea mai simpl i este cea folosit de scriptul dumpjnembers. Activarea atributului RaiseError poate fi inadecvat dac dorii s executai un program personal de curenie atunci cnd scriptul i ncheie execuia, dei n acest caz putei efectua operaiile dorite redefinind variabila de manipulare $SIG{_DIE_}. Un alt motiv pentru care dorii s evitai activarea atributului RaiseError este acela c DBI afieaz informaii de natur tehnic n mesajele sale, dup cum urmeaz: disconnect (DBI: :db=HASH(Ox197aae4) ) invalidates 1 active statement. Either destroy statement handles or call finish on them before disconnecting. (Instruciunea ... invalideaz l instruciune activ. Distrugei variabilele de manipulare pentru instruciuni sau apelai funcia finish ( ) pentru acestea nainte de deconectare.) Acestea constituie informaii utile pentru un programator, dar nu este genul de lucruri pe care utilizatorul cotidian dorete s le vad. n acest caz, este mai bine s verificai personal apariia erorilor, astfel nct s putei afia mesaje mai semnificative pentru persoanele care vor utiliza scriptul. Alternativ, putei lua n considerare redefinirea variabilei de manipulare $SIG{ _ DIE _ }. Acest lucru poate fi util, deoarece v permite s activai RaiseError pentru simplificarea procedurii de tratare a erorilor, dar i s nlocuii mesajele de eroare prestabilite pe care le prezint DBI cu propriile dumneavoastr mesaje. Pentru a v furniza propria variabil de manipulare _ DIE _ , nainte de a executa vreun apel DBI, procedai astfel: $SIG{ _ DIE _ } = sub {die "Scuze, s-a produs o eroareVn"; }; De asemenea, putei declara o subrutin n maniera uzual i putei stabili valoarea variabilei de manipulare folosind o referin la subrutin: sub die_handler {

die "Scuze, s-a produs o eroare\n"; $SIG{_DIE_} = \&die_handler; Ca alternativ la transferul n form literal al atributelor operaiei de tratare a erorilor n cadrul apelului la funcia connect ( ), acestea pot fi definite folosind o funcie hash i transfernd o referin la hash. Unii sunt de prere c extinderea" valorilor atributelor n acest mod faciliteaz citirea si editarea scripturilor, dar cele dou metode sunt identice din punct de vedere funcional. Iat un exemplu care prezint modul de utilizare al valorii hash al unui atribut: 302 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL %attr= PrintError => O, RaiseError => O ); $dbh = DBI->connect ($dsn, $user_name, Spassword, \%attr) or die "Nu se poate conecta la server: $DBI::err ($DBI::errstr)\n"; Scriptul urmtor, intitulat dump_members2, ilustreaz modul de scriere a unui script atunci cnd dorii s verificai personal apariia erorilor si s v scriei propriile mesaje, dumpjnem-bers2 prelucreaz aceeai interogare ca i dumpjnembers, dar dezactiveaz n mod explicit PrintError i RaiseError, dup care testeaz rezultatul fiecrui apel DBI. Cnd se produce o eroare, scriptul invoc subrutina bail_out() care afieaz un mesaj, respectiv coninutul variabilelor $DBI:: err i $DBI:: errstr nainte de a-si ncheia execuia: #! /usr/bin/perl # dutnp_members2 - afieaz lista membrilor Ligii istorice use DBI; use strict; my ($dsn) = "DBI:mysql:samp_db:localhost"; # nume sursa date my ($user_name) = "paul"; # numele utilizatorului my ($password) = "secret" # parola # variabile pentru baza de date si instruciune # tablou pentru rndurile returnate de interogare # atributele de tratare a erorilor my ($dbh, $sth); my (@ary); my (%attr) = PrintError => O, RaiseError => O # conectarea la baza de date $dbh = DBI->connect ($dsn, $user_name, Spassword, \%attr) or bail_out ("Nu se poate conecta la baza de date"); # emiterea interogrii $sth = $dbh->prepare ("SELECT nume, prenume, sufix, email, . "strada, ora, stat, cod_postal, telefon FROM membru" "ORDER BY nume") or bail_out ("Nu poate prepara interogarea"); $sth->execute () or bail_out ("Nu poate executa interogarea"); Capitolul 7 Interfaa API pentru Perl DBI 303 # citete rezultatele interogrii while (@ary = $sth->fetchrow_array()) { print join ("\t", @ary), "\n"; } $DBI::err == O or bail_out ("Eroare in timpul regsirii"); # curenie $sth->finish () or bail_out ("Nu poate finaliza interogarea"); $dbh->disconnect () or bail_out ("Nu se poate deconecta de la baza de date"); exit (0); # subrutina bail_out() - afieaz cod de eroare si irul de eroare, apoi isi ncheie execuia sub bail_out my ($message) = shift; die "$message\nError $DBI::err ($DBI::errstr)\n"); Funcia bail_out()este similar cu funcia print_error() pe care am folosit-o pentru scrierea programelor C din capitolul 6, cu deosebirea c bail_out () i ncheie execuia, nu returneaz controlul apelantului. bail_out () v scutete de efortul de a scrie numele variabilelor $DBI:: err i $OBI:: errstr de fiecare dat cnd dorii s scriei un mesaj de eroare. De asemenea, prin ncapsularea afirii mesajelor ntr-o subrutin, putei schimba n mod uniform formatul mesajelor dumneavoastr de eroare n ntreg scriptul, efectund o modificare n subrutin. Scriptul dumpjnembers include dup ciclul de preluare a rndurilor un test pe care scriptul dumpjnembers nu-1 conine. Deoarece dump_members2 nu-i ncheie automat execuia dac se produce vreo eroare n tabloul fetchrow_array (), este mai prudent s se determine dac ciclul s-a terminat datorit citirii complete a setului de

nregistrri (terminare normal) sau datorit producerii unei erori. Ciclul se ncheie n ambele situaii, desigur, dar, n cazul apariiei unei erori, datele de ieire ale scriptului vor fi trunchiate, n absena unei verificri a apariiei erorilor, persoana care ruleaz scriptul nu va avea nici cel mai mic indiciu c s-ar fi ntmplat ceva! Dac verificai personal apariia erorilor, nu uitai s testai rezultatul ciclurilor de preluare. 304 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Tratarea interogrilor care nu returneaz nici un set de rezultate Instruciuni precum DELETE, INSERT, REPLACE si UPDATE, care nu returneaz rnduri, sunt relativ simplu de prelucrat n comparaie cu instruciuni precum SELECT, DESCRIBE, EXPLAIN i SHOW, care returneaz rnduri. Pentru a prelucra o instruciune care nu este de tip SELECT, transferai-o funciei do() folosind variabila de manipulare pentru baze de date. Metoda do( ) prepar i execut interogarea ntr-o singur etap. De exemplu, pentru a introduce rubrica aferent unui membru nou, pe nume Cornel Vasile, cu data expirrii 3 iunie 2002, putei proceda astfel: $rows = $dbh->do ("INSERT membru ( nume, prenume, expirare )" . " VALUES (' Vasile1, 'Cornel1 , '2002-6-3')"); Metoda do( ) returneaz un numr al rndurilor afectate, respectiv undef dac se produce vreo eroare. O eroare se poate produce din diverse motive. (De exemplu, interogarea este deformat sau dumneavoastr nu avei permisiunea de a obine acces la tabel.) Pentru o valoare returnat diferit de undef, fii atent la situaia n care nu este afectat nici un rnd. Cnd survine o atare situaie, funcia do() nu returneaz numrul 0; n schimb, returneaz irul "OEO" (notaia tiinific folosit n Perl pentru zero). "OEO" are valoarea O ntr-un context numeric, dar este considerat ca adevrat" n teste condiionale, astfel nct s poat fi deosebit cu uurin de undef. Dac funcia do( ) ar fi returnat O, atunci ar fi fost mai dificil s se fac diferena dintre apariia unei erori (undef) i cazul n care nu a fost afectat nici un rnd. Putei verifica apariia unei erori folosind oricare din urmtoarele teste: if (idefined ($rows)) { # eroare } if (!$rows) { # eroare } n contexte numerice, "OEO" are valoarea 0. Programul urmtor va afia corect numrul rndurilor pentru orice valoare a variabilei $rows diferit de undef: if (!$rows) { prin "eroare\n"; } else < $rows += 0; # conversie forat la numr daca "OEO" prin "$rows rnduri afectate\n"; } _ De asemenea, putei afia valoarea variabilei $rows folosind un format %d cu funcia, printf ( ) pentru a fora o conversie implicit la un numr: if (!$rows) { prin "eroare\n"; } else print "%d $rows rnduri afectate\n", $rows; Capitolul 7 Interfaa API pentru Perl DBI 305 Metoda do () este echivalent cu prepare (), urmat de execute (). Instruciunea INSERT anterioar poate fi emis i astfel, nu numai prin apelarea funciei do (): $sth = $dbh->prepare("INSERT membru (nume.prenume,expirare)" . " VALUES('Vasile','Cornel','2002-63')"); $rows = $sth->execute (); Tratarea interogrilor care returneaz un set de rezultate Aceast seciune furnizeaz mai multe informaii despre numeroase opiuni pe care le avei la dispoziie n vederea executrii ciclului de preluare a rndurilor pentru interogrile SELECT (sau pentru alte interogri de tip SELECT care returneaz rnduri, precum DESCRIBE, EXPLAIN si SHOW). De asemenea, se discut despre obinerea numrului de rnduri ale unui rezultat, modul de tratare a seturilor de rezultate pentru care nu este necesar nici un ciclu, respectiv despre regsirea dintr-o micare" a unui ntreg set de rezultate. Scrierea ciclurilor de preluare a rndurilor Scriptul dumpjnembers regsea date folosind o secven standard de metode DBI: pre-pare() pentru a permite driverului s pre-proceseze interogarea, execute() pentru a ncepe execuia interogrii, f etchrow_array () pentru a prelua fiecare rnd al setului de rezultate, respectiv finish() pentru a elibera resursele alocate interogrii. prepare(), execute!) i finish() sunt componente oarecum standard ale prelucrrii oricrei interogri care returneaz rnduri. Totui, pentru preluarea rndurilor, f etchrow_array () este numai o opiune din alte numeroase metode (vezi tabelul 7.3). Tabelul 7.3 Metode DBI pentru preluarea rndurilor

Numele metodei fetchrow_array() fetchrow_arrayref() fetch() fetchrowjiashref() Valoare returnat Tablou cu valorile din rnd Referin la un tablou cu valorile din rnd Similar cu f etchrow_arrayref () Referin la valorile hash ale valorilor din rnd, care conin si numele coloanei drept cheie Exemplele urmtoare prezint modul de utilizare a fiecrei metode de preluare a rndurilor. Exemplele parcurg ciclic rndurile unui set de rezultate i, pentru fiecare rnd, afieaz valorile din coloane separate prin virgule. Exist metode mai eficiente de a scrie codul de afiare n anumite situaii, dar exemplele sunt astfel scrise pentru a ilustra sintaxa necesar accesului la valorile individuale din coloane. f etchrow_array ( ) este folosit dup cum urmeaz: while (@ary = $sth->fetchrow_array ()) $delim = Continuare

306 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare for ($i = 0; $i < @ary; $i++) { prin $delim . $ary[$i]; $delim = ","; } prin "\n"; } Fiecare apel la funcia fetchrow_array() returneaz un tablou de valori din rnd, respectiv un tablou vid dac nu mai exist rnduri. Ca alternativ la atribuirea valorii returnate unei variabile de tip tablou, putei prelua valorile din coloane ntr-un set de variabile scalare. Putei face aceasta dac dorii s lucrai cu nume de variabile care sunt mai semnificative dect $ary [0], $ary[ 1 ] etc. S presupunem ca dorii s regsii valorile numelor i ale adreselor de e-mail n variabile. Folosind f etchrow_array(), putei selecta si prelua rnduri astfel: $sth = $dbh->prepare ("SELECT nume, prenume, sufix, email," . "strada, ora, stat, cod_postal, telefon FROM membru" . "ORDER BY nume"); $sth->execute (); while (($nume, $prenume, $sufix, $email) = $sth->fetchrow_array()) { # se executa operaii cu variabilele } Cnd folosii o list de variabile n acest mod, trebuie s v asigurai c interogarea dumneavoastr selecteaz coloanele n ordinea corect, desigur. DBI nu cunoate ordinea n care sunt denumite coloanele n instruciunea dumneavoastr SELECT, deci dumneavoastr trebuie s atribuii variabilele corect. De asemenea, putei determina ca variabilele individuale s primeasc n mod automat valorile coloanelor atunci cnd selectai un rnd, folosind o tehnic cunoscut sub numele de asociere a parametrilor. Despre aceasta vom discuta mai detaliat n seciunea Cmpuri de nlocuire i asocierea parametrilor". fetchrow_arrayref () este similar cu fetchrow_array() dar, n loc de a returna un tablou care conine valorile coloanelor pentru rndul curent, pur si simplu returneaz o referin la tablou, respectiv undef atunci cnd nu mai exist rnduri. Se folosete astfel: while ($ary_ref = $sth->fetchrow_arrayref()) $delim = ""; for ($i = 0; $i { prin Sdelim $delim = ","; }

prin "\n"; @{$ary_ref}; $ary_ref->[$i]; Capitolul 7 Interfaa API pentru Perl DBI 307 Elementele tabloului sunt accesibile prin intermediul referinei la tablou, $ary_ref. Aceasta este oarecum asemntor cu determinarea entitii indicate de un pointer, deci folosii $ary_ref ->[$i] n loc de $ary[$i]. Pentru a trata referina ca tablou complet, folosii construcia @{$ary_ref }. fetchrow_arrayref () nu este adecvat pentru preluarea variabilelor ntr-o lista. De exemplu, ciclul urmtor nu funcioneaz: while (($nume, Sprenume, $sufix $email) = @{$sth->fetchrow_arrayref()}) { # se utilizeaz variabilele intr-un anumit mod } Atta timp ct fetchrow_arrayref () preia efectiv un rnd, ciclul funcioneaz corect. Cnd ns rndurile se termin, f etchrow_arrayref () returneaz undef, iar @{undef } nu este o variabil corect. (Este similar cu ncercarea de determinare a entitii indicate de un pointer NULL ntr-un program C.) Cea- de-a treia metod de preluare a rndurilor, f etchrowjiashref (), se folosete astfel: while (Shashref = $sth>fetchrowjiashref()) { Sdelim = ""; foreach $key (keys (%{$hashref})) { prin $delim . $hashref->{$key}; $delim = ","; } prin "\n"; } Fiecare apel la funcia f etchrowjiashref () returneaz o referin la un hash al valorilor unui rnd, ordonate dup numele coloanelor, respectiv undef dac nu mai sunt rnduri, n acest caz, valorile coloanelor nu sunt dispuse ntr-o ordine anumit; membrii funciilor hash din Perl nu sunt ordonai. Cu toate acestea, elementele hash sunt ordonate n funcie de numele coloanei, deci Shashref v ofer o singur variabil prin intermediul creia putei obine acces la orice valoare de coloan, n funcie de nume. Aceasta v permite s extragei valori (sau orice subset de valori) i nu trebuie s cunoatei ordinea n care interogarea SELECT a regsit coloanele. De exemplu, dac dorii s obinei acces la cmpurile cu numele si adresa de e-mail, procedai astfel: while (Shashref = $sth->fetchrow_hashref()) { $delim = ""; foreach $key ("nume", "prenume", "sufix", "email") { prin $delim . $hashref->{$key}; $delim = ","; } prin "\n"; It. 4 S 308 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Funcia f etchrowjiashref () este util mai ales cnd dorii s transferai nei funcii un rnd de valori, fr a impune funciei s cunoasc ordinea n care au fost denumite coloanele n instruciunea SELECT, n acest caz, vei apela funcia f etchrowjiashref {) pentru regsirea rndurilor i vei scrie o funcie care obine acces la valorile din hash-ul rndului, folosind numele coloanelor. Reinei urmtoarele pericole atunci cnd folosii f etchrowjiashref (): Dac avei nevoie de maximum de performan, f etchrow_hashref () nu este cea mai bun opiune, deoarece nu este la fel de eficient ca fetchrow_array() sau fetchrow_arrayref(). Numele coloanelor folosite ca valori cheie n hash respect mrimea literelor folosite la scrierea coloanelor n instruciunea SELECT, n MySQL, numele coloanelor nu sunt sensibile la diferena ntre majuscule si minuscule, deci interogarea va avea acelai efect, indiferent de mrimea literelor folosite pentru scrierea numelor coloanelor. Dar numele elementelor hash cheie din Perl sunt sensibile la diferena ntre majuscule i minuscule, fapt ce v poate provoca probleme. Pentru a evita posibile probleme legate de neconcordane ntre mrimile literelor, putei cere funciei f etchrowjiashref () s foreze scrierea numelor coloanelor folosind o anumit mrime de liter, transfern-du-i acesteia un atribut NAME_lc sau NAME_uc: $hash_ref = $sth->fetchrowjiashref ('NAME_lc'); # folosete nume scrise cu minuscule $hash_ref = $sth->fetchrowjiashref ('NAME_uc'); # folosete nume scrise cu majuscule

Hash-ul conine cte un element pentru fiecare nume distinct de coloan. Dac efectuai o unire care returneaz coloane din mai multe tabele cu nume care se suprapun, nu vei putea avea acces la toate valorile din coloane. De exemplu, dac emitei urmtoarea interogare, f etchrowjiashref () va returna un hash avnd un singur element: SELECT a.nume, b.nume FROM a, b, WHERE a.nume = b.nume Determinarea numrului de rnduri returaate de o interogare Cum putei determina numrul de nregistrri returnate de o interogare SELECT sau de tip SELECT? O modalitate este aceea de a numra rndurile pe msur ce le preluai, desigur. De fapt, aceasta este singura metod portabil de a cunoate numrul de rnduri pe care le returneaz o interogare SELECT. Utiliznd driverul MySQL, putei apela metoda rows() folosind variabila de manipulare a instruciunii dup invocarea funciei execute (), dar acest procedeu nu este portabil la alte motoare de baze de date. Nici mcar n MySQL rows () nu returneaz rezultatul corect dect atunci cnd ai preluat toate rndurile, dac ai activat atributul mysql_use_result. (Pentru mai multe informaii, vezi Anexa G.) Deci, puteri la fel de bine numra rndurile pe msur ce le preluai. Preluarea rezultatelor compuse dintr-un singur rnd Nu este necesar s rulai un ciclu pentru obinerea rezultatelor, dac setul de rezultate const dintr-un singur rnd. S presupunem c dorii s scriei un script countjnembers, care v indic numrul curent al membrilor Ligii istorice. Codul pentru efectuarea interogrii are urmtorul aspect: : Capitolul? Interfaa AP! pentru Pert DBI 309 # emite interogarea $sth = $dbh->prepare ("SELECT COUNT (*) FROM membru") ; $sth->execute (); # citete rezultatele interogrii, apoi face curenie Scount = $sth->fetchrow_array (); $sth->finish (); $count = "nedeterminat" if Idefined- (count); prin "$count\n"; Instruciunea SELECT va returna un singur rnd, deci nu este necesar un ciclu; vom apela funcia fetchrow_array( ) numai o dat. n plus, deoarece selectm o singur coloan, nu este necesar nici mcar atribuirea valorii returnate unui tablou. Cnd funcia f etchrow_array ( ) este apelat ntr-un context scalar (unde se ateapt o singur valoare, nu o list ntreag), va returna valoarea din prima coloan a rndului, respectiv undef dac nu mai exist rnduri disponibile. Un alt tip de interogri pentru care este de ateptat cel mult o singur nregistrare este cel care conine LIMIT 1 , pentru limitarea numrului de rnduri returnate. O utilizare comun a acestui atribut este pentru returnarea rndului care conine valoarea minim, respectiv maxim a unei anumite coloane. De exemplu, interogarea urmtoare afieaz numele si data naterii preedintelui american care s-a nscut cel mai recent: $query = "SELECT nume, prenume, data_nastere" . "FROM preedinte ORDER BY data_nastere DESC LIMIT 1"; $sth = $dbh->prepare ($query); $sth->execute () ; # citete rezultatele interogrii, apoi face curenie ($nume, Sprenume, $data_nastere) = $sth->fetchrow_array (); $sth->finish () ; if ('.defined ($nume)) { " ' ' ..... ' print "Interogarea nu a returnat nici un rezultatVn"; } "' l; else ^ prin "Preedintele nscut cel mai recent: "' nume Sprenume ($data_nastere)\n"; } ' : ' " : ' "'"'' "' ...... Alte tipuri de interogri pentru care nu este necesar nici un cichi de preluare sunt cele care folosesc MAX() sau MIN() pentru a sdecta o singur valoare. Dar, n toate aceste siturii, o modalitate chiar mai simpl de a obine un rezuhat cu un singur rnd este de a folosi'metoda cu variabila de manipulare pentru baze de date select row_array ( ), care combina funciile prepare ( ), execute ( ) si rutina de preluare a rndurilor ntr-un singur apel. Funcia selectrow_array () returneaz un tablou (nu o referin), respectiv un 310 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL tablou vid n cazul apariiei unor erori. Exemplul anterior poate fi scris astfel, folosind funcia selectrow_array(): $query = "SELECT nume, prenume, data_nastere" . " FROM preedinte ORDER BY data_nastere DESC LIMIT 1"; ($nume, $prenume, $data_nastere) = $sth->selectrow_array (Squery); if (Idefined ($nume)) { prin "Interogarea nu a returnat nici un rezultat\n";

} else { print "Preedintele nscut cel mai recent: $nume Sprenume ($data_nastere)\n"; } Lucrul cu seturi de rezultate complete Cnd folosii un ciclu de preluare, DBI nu furnizeaz vreo modalitate de cutare aleatoare n setul de prelucrare i nici o metod de prelucrare a rndurilor n orice alt ordine dect cea n care le-a returnat ciclul. De asemenea, dup ce preluai un rnd, rndul anterior se pierde, dac nu luai msuri pentru a-1 pstra n memorie. Aceast comportare nu este ntotdeauna adecvat: Putei dori s prelucrai rnduri ntr-o ordine non-secvenial. S considerm o situaie cnd dorii s alctuii un chestionar care se bazeaz pe datele despre preedinii americani menionate n tabelul preedinte al Ligii istorice. Dac dorii s punei ntrebri ntr-o alt ordine la fiecare prezentare a chestionarului, putei selecta toate rndurile din tabelul preedinte. Apoi, putei selecta rnduri ntr-o ordine aleatoare, pentru a varia ordinea preedinilor despre care punei ntrebrile. Pentru a selecta un rnd n mod aleator, trebuie s avei acces simultan la toate rndurile. Putei dori s folosii numai un subset al rndurilor returnate, subset selectat aleator. De exemplu, pentru a prezenta o ntrebare cu opiuni multiple cu privire la locul naterii unui preedinte, putei selecta aleator un rnd pentru a selecta preedintele (si rspunsul corect), iar apoi alegei alte rnduri din care s extrageri opiunile eronate. Putei dori s obinei ntregul set de rezultate, chiar dac l prelucrai n ordine secvenial. Acest l acru poate fi necesar dac trebuie s executai mai multe parcurgeri ale rndurilor. De exemplu, ntr-un calcul statistic, putei parcurge o dat setul de rezultate pentru a evalua unele proprieti numerice generale ale datelor dumneavoastr, iar apoi parcurgei rndurile din nou, executnd o analiz cu un caracter mai specific. Putei obine acces la setul de rezultate n totalitatea sa n dou moduri diferite. Putei efectua ciclul de preluare obinuit i salvai fiecare rnd pe msur ce l preluai, fie putei folosi o metod care returneaz un ntreg set de rezultate dintr-o dat. Indiferent cum procedai, vei obine o matrice care conine un rnd pentru fiecare rnd al setului de rezultate, respectiv numrul de coloane pe care le-ai selectat. Putei prelucra elementele matricei n orice ordine dorii, de oricte ori dorii. Expunerea urmtoare descrie ambele metode. Capitolul 7 Interfaa API pentru Perl DBI 311 O modalitate de a folosi un ciclu de preluare pentru capturarea setului de rezultate este de a folosi funcia f etchrow_array () i de a salva un tablou cu referine la rnduri. Programul urmtor procedeaz ntr-un mod similar cu ciclul de preluare-afisare din scriptul dump_members, cu deosebirea c salveaz toate rndurile, apoi afieaz matricea pentru a ilustra modul de determinare a numrului de rnduri i coloane din matrice, precum i modul de acces la membrii individuali ai matricei, my (Omatrix) = (); # tablou de referine la tablou while (my @ary = $sth->fetchrow_array ()) # preia fiecare rnd { push (matrix, t @ary ]); # salveaz referina la rndul preluat } $sth->finish (); # determina dimensiunile matricei my (grows) = scalar (matrix); my (@cols) = ($rows == O ? O : scalar (@{$matrix[0]})); for (my $i = 0; $i < $rows ; $i++) # afieaz fiecare rnd { my ($delim) = ""; for (my $j = 0; $3 < $cols ; $]'++) { prin $delim . $matrix[$i][$j]; $delim = ","; } prin "\n"; } Cnd stabilii dimensiunile matricei, numrul rndurilor trebuie s fie determinat primul, deoarece calculul numrului de coloane depinde de faptul dac matricea este vid sau nu. Dac $rows este O, matricea este vid i $cols devine de asemenea 0. n caz contrar, numrul coloanelor poate fi calculat ca fiind numrul elementelor din tabloul aferent unui rnd, folosind sintaxa @{$matrix[$i]} pentru a avea acces la rndul $i n totalitatea sa. n exemplul anterior, am preluat fiecare rnd, apoi am salvat o referin la acesta. Ai putea presupune c este mai eficient s apelai funcia fetchrow_arrayref () n loc s regsii direct referinele la rnduri: my (matrix) = (); # tablou de referine la tablou while (my ary_ref =$sth->fetchrow_arrayref ()) { # acest procedeu nu funcioneaz push (matrix, @ary_ref); # salveaz referina la rndul preluat }

$sth->finish (); 312 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Metoda de mai sus nu funcioneaz, deoarece fetchrow_arrayref () refoloseste tabloul spre care indic referina. Matricea rezultant este un tablou de referine, unde fiecare referin indic spre acelai rnd - rndul regsit ultimul. Ca atare, dac dorii s preluai rnd cu rnd, folosii f etch row_ar ray () n loc de f etchrow_arrayref (). Ca alternativ la utilizarea unui ciclu de preluare, putei folosi una dintre metodele DBI care returneaz ntregul set de rezultate. De exemplu, f etchall_arrayref () returneaz o referin la un tablou de referine, unde fiecare din aceste referine indic spre coninutul unui rnd al setului de rezultate. Este puin, dar, ca efect, valoarea returnat este o referin la o matrice. Pentru a folosi f etchall_arrayref (), apelai prepare () i execute (), apoi regsii rezultatul astfel: my ($matrix_ref); # referina la un tablou de referine $matrix_ref = $sth->fetchall_arrayref (); # preia toate rndurile # determina dimensiunile matricei my (Srows) = (Sdefined ($matrix_ref) ? O : scalar (@{$matrix_ref>)); my ($cols) = ($rows == O ? O : scalar (0{$matrix_ref->[0)})); for (my $i = 0; $i < $rows; $i++) my ($delim) = ""; for (my $j = 0; $j < $cols; $]++) prin $delim . $matrix_ref->[$i][$j]; $delim = "."; # afieaz fiecare rnd prin "\n"; Funcia fetchall_arrayref () returneaz o referin la un tablou vid, dac setul de rezultate este vid. Rezultatul este undef dac survine o eroare deci, dac atributul RaiseError nu este activat, nu uitai s verificai valoarea returnat nainte de a ncepe s o folosii. Numrul de rnduri i de coloane este determinat de faptul dac matricea este sau nu vid. Dac dorii s obinei acces la un ntreg rnd $i al matricei ca la un tablou, folosii sintaxa @{$matrix_ref->[i]}. Este categoric mai simplu s se foloseasc f etchall_arrayref () pentru a regsi un set de rezultate dect s se scrie un ciclu de preluare a rndurilor, dei sintaxa pentru accesul la elementele tabloului devine puin mai complicat. O metod similar cu fetchall_arrayref (), dar care execut mai multe operaii, este selectall_arrayref (). Aceast metod execut automat ntreaga secven format din prepare(), execute{), ciclul de preluare i finish(). Pentru a folosi selectall_arrayref(), transferai interogarea direct acestei funcii, folosind variabila de manipulare pentru baze de date: Capitolul 7 Interfaa API pentru Perl DBI 313 my ($matrix_ref); # referina la tablou de referine $matrix_ref = $dbh->selectall_arrayref ("SELECT nume, prenume, sufix," . "email, strada, ora, stat, cod_postal, telefon FROM " . "membru ORDER BY nume"); # determina dimensiunile matricei my ($rows) = (tdefined ($matrix_ref) ? O : scalar (@{$matrix_ref})); my ($cols) = (Srows == O ? O : scalar (@{$matrix_ref->[0]})); for (my $i = 0; $i < $rows; $i-n-) # afieaz fiecare rnd my ($delim) = ""; for (my $j = 0; $j < $cols; prin $delim . $matrix_ref->[$i][$j]; $delim = ","; prin "\n"; Verificarea valorilor NULL Cnd regsii date dintr-o baz de date, poate fi necesar s facei diferena ntre valorile coloanelor care sunt NULL i valorile zero sau irurile vide. Acest lucru este uor de fcut, deoarece DBI returneaz valorile NULL ca undef. Totui, trebuie s fii sigur c folosii testul corect. Dac ncercai urmtorul fragment de program, va afia "fals J" de toate cele trei ori: $col_val = undef; if (!$col_val) { prin "fali\n"; } $col_val = 0; if (l$col_val) { prin "fali\n"; } $col_val = ""; if (!$col_val) { prin "fals!\n";} De asemenea, i acest fragment afieaz "fals!" la ambele teste: $col_val = undef; if ($col_val eq "") { prin "fals!\n"; } $col_val = ""; if ($col_val eq "") { prin "falsl\n"; } Acest fragment are acelai efect: $col_val = ""; if ($col_val eq "") { prin "fals!\n"; } if ($col_val eq == 0) { prin "fals!\n"; } Pentru a face diferena ntre valorile NULL si valorile diferite de NULL din coloane, folosii def ined(). Dup ce ai aflat c o valoare nu reprezint NULL, putei face diferena ntre alte tipuri de valori folosind testele adecvate.

De exemplu: if ((defined ($col_val) { print "NULLAn'; } elsif ($col_val eq "") { print "sir vid\n"; } elsif ($col_val == 0) { prin "zero\n"; } else { print "alta valoare\n"; } 314 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Este important s efectuai testele ntr-o ordine corespunztoare, deoarece att a doua, ct i a treia comparaie sunt adevrate dac $col_val este un ir vid. Dac inversai ordinea acestor comparaii, vei identifica n mod incorect irurile vide ca fiind zero, Probleme legate de ghilimele Pn acum, am construit interogri n cel mai elementar mod cu putin, folosind iruri ncadrate ntre ghilimele simple. Acest fapt creeaz o problem la nivelul lexical al limbajului Perl, atunci cnd irurile dumneavoastr ncadrate ntre ghilimele conin valori de asemenea ncadrate ntre ghilimele. De asemenea, putei avea probleme la nivelul SQL, cnd dorii s inserai sau s selectai valori care conin ghilimele, backslash-uri sau date binare. Dac specificai o interogare sub forma unui ir Perl delimitat prin ghilimele, trebuie s modificai semnificaia tuturor apariiilor caracterului de citare n interiorul irului de interogare: $query = 'INSERT absente VALUES(14,\'1999-9-16\')'; Squery = "INSERT absente vALUESU.Viggg-g-ieV')"; Att Perl, ct si MySQL v permit s ncadrai iruri ntre ghilimele, folosind ghilimele simple sau duble, deci uneori putei evita modificarea semnificaiei unor caractere prin combinarea semnelor citrii: Squery = 'INSERT absente vALUES(14,"1999-9-16")'; $query = "INSERT absente VALUES(14,'1999-9-16')"; Cu toate acestea, cele dou tipuri de ghilimele nu sunt echivalente n Perl. Referinele la variabile sunt interpretate numai n interiorul ghilimelelor duble. Ca atare, ghilimelele simple nu sunt foarte utile atunci cnd dorii s construii interogri prin nglobarea referinelor la variabile n irul de interogare. De exemplu, dac valoarea variabilei $var este 14, urmtoarele dou iruri nu sunt echivalente: "SELECT * FROM membru WHERE id = $var" 'SELECT * FROM membru WHERE id = $var' irurile sunt interpretate dup cum urmeaz; evident, primul ir este cel care poate fi trimis unui server MySQL: "SELECT * FROM membru WHERE id = 14" 'SELECT * FROM membru WHERE id = $var' O alternativ la ncadrarea irurilor ntre ghilimele duble const n utilizarea construciei qq{}, care indic programului Perl s trateze ntreg textul cuprins ntre qq{ si } ca pe un ir ncadrat ntre ghilimele duble. (Gndii-v c qq nseamn ghilimele duble.) De exemplu, urmtoarele dou linii sunt echivalente: $data = "1999-9-16"; $data = qq{1999-9-16}; Putei construi interogri fr a acorda prea mare atenie ghilimelelor dac folosii qq{}, deoarece putei folosi ghilimelele (simple sau duble) n cadrul irului de interogare fr a fi necesar s le modificai semnificaia. De asemenea, referinele la variabile sunt interpretate. Ambele proprieti ale construciei qq{} sunt prezentate n urmtoarea interogare: $id .= 14; $data = "1999-9-16"; $interogare = qq{INSERT absente VALUES($id,"$data")}; Capitolul? Interfaa API pentru Perl DBI 315 Nu trebuie s folosii paranteze acolade ca delimitatori pentru construcia qq. Alte forme, precum qq() i qq/ / sunt de asemenea utilizabile, cu condiia ca delimitatorul de nchidere s nu fie coninut n interiorul irului. Eu prefer qq{}, deoarece este mai puin probabil ca paranteza acolad s apar n textul irului interogrii dect paranteza. rotund sau caracterul /, dup cum este mai puin probabil s fie confundat cu finalul irului de interogare dect ultimele dou caractere. De exemplu, paranteza rotund apare n cadrul instruciunii INSERT prezentate anterior, deci qq() nu este o construcie util pentru ncadrarea ntre ghilimele a irului interogrii. Construcia qq{} se poate extinde pe mai multe rnduri, ceea ce este util dac dorii ca irul interogrii s ias n eviden n raport cu liniile de program Perl nconjurtoare: $id = U; $data = "1999-9-16"; Sinterogare = qq{ {INSERT absente VALUES($id,"$data") }; Acest lucru este de asemenea util dac dorii pur i simplu s formatai interogarea pe linii multiple, pentru a o face mai uor de citit. De exemplu, instruciunea SELECT din scriptul dumpjnembers se prezint astfel:

$sth = $dbh->prepare ("SELECT nume, prenume, sufix, email," . "strada, ora, stat, cod_postal, telefon FROM membru" . "ORDER BY nume"); Folosind construcia qq{}, interogarea poate fi scris astfel: $sth = $dbh->prepare (qq{ SELECT nume, prenume, sufix, email, strada, ora, stat, cod_postal, telefon FROM membru ORDER BY nume }); Este la fel de adevrat c irurile ncadrate ntre ghilimele duble se pot i ele extinde pe mai multe linii. Dar eu prefer mai mult construcia qq{} pentru scrierea de iruri pe linii multiple. Atunci cnd gsesc ntr-o linie nite ghilimele duble fr corespondent, prima mea reacie este: Nu cumva e o eroare de sintax?" Apoi, pierd vremea cutnd ghilimelele corespondente. Construcia qq{} se ocup de problemele legate de ghilimele la nivelul lexical al limbajului Perl, astfel nct s putei insera cu uurin ghilimele ntr-un ir, fr ca Perl s protesteze. Totui, trebuie s v gndii i la sintaxa la nivel SQL. S lum n considerare aceast ncercare de a insera o nregistrare n tabelul membru: $nume = "O'Malley"; $prenume = "Brian"; $data_expirare = "2002-9-1"; $rows = $dbh->do (qq{ Continuare 316 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare INSERT membru (nume,prenume,data_expirare) VALUES('$nume','Sprenume','$data_expirare') }); irul pe care funcia do () l trimite serverului MySQL se prezint astfel: INSERT membru (nume,prenume,data_expirare) VALUESCO'Malley' , 'Brian', '2002-9-1') Aceasta nu este o interogare SQL corect, deoarece un caracter de tip ghilimele simple apare n interiorul unui ir delimitat prin ghilimele simple. Am ntlnit o problem similar n capitolul 6. Acolo, am rezolvat problema cu ajutorul funciei mysql_escape_string(). DBI furnizeaz un mecanism similar: pentru fiecare valoare cu ghilimele pe care dorii s o folosii literal ntr-o instruciune, apelai metoda quote () si folosii n schimb valoarea returnat de aceasta. Exemplul anterior poate fi scris ntr-un mod mai adecvat astfel: $nume = $dbh->quote ("O'Malley"); $prenume = $dbh->quote ("Brian"); $data_expirare = $dbh->quote ("2002-9-1'); $rows = $dbh->do (qq{ INSERT membru (nume,prenume,data_expirare) VALUES($nume,$prenume,$data_expirare) }); Acum, irul pe care funcia do() l trimite serverului MySQL se prezint astfel, ghilimelele care apar n interiorul irului fiind camuflate" adecvat pentru server: INSERT membru (nume,prenume,data_expirare) VALUES('OVMalley', 'Brian1 ,'2002-9-1') Observai c, atunci cnd facei referire la $nume i $prenume n irul interogrii, nu adugai ghilimele de delimitare; metoda quote () le furnizeaz automat. Dac adugai ghilimele, interogarea dumneavoastr va avea prea multe ghilimele, aa cum se poate vedea n exemplul urmtor: $value = "paul"; $quoted_value = $dbh->quote ($value); prin " ... WHERE nume = $quoted_value\n"; prin " ... WHERE nume = '$quoted_value'\n"; Aceste instruciuni furnizeaz urmtoarele date de ieire: ... WHERE nume = 'paul' ... WHERE nume = ''paul'' Cmpuri de nlocuire i asocierea parametrilor n seciunile anterioare, am construit interogri prin plasarea valorilor ce urmau a fi inserate n baza de date sau folosite drept criterii de selecie direct n irul interogrii. Acest lucru nu este necesar. DBI v permite s plasai ntr-un ir de interogare indicaCapitolul? Interfaa API pentru Peri DBI 317 toare speciale, denumite cmpuri de nlocuire, iar apoi s furnizai, la executarea interogrii, valorile ce vor fi folosite n locul acestor indicatoare. Principalul motiv pentru aceast operaie l constituie mbuntirea performanelor, mai ales atunci cnd executai n mod repetat o interogare, n cadrul unui ciclu. Ca o ilustrare a modului de lucru a cmpurilor de nlocuire, s presupunem c ncepei un nou semestru la coal i dorii s tergei tabelul elev pentru catalogul dumneavoastr si apoi s-1 iniializai cu noii elevi, folosind o list a numelor elevilor incluse ntr-un fiier, n lipsa cmpurilor de nlocuire, putei terge coninutul curent al tabelului i ncrcai noile nume astfel: $dbh->do (qq{ DELETE FROM elev } ); # terge rndurile existente while (<>) # adaug rnduri noi { chomp;

$_ = $dbh->quote ($_); $dbh->do (qq{ INSERT elev SET nume = $_}); } Acest procedeu este ineficient, deoarece forma elementar a interogrii INSERT este aceeai de fiecare dat, iar funcia do() apeleaz prepare () i execute () de fiecare dat pe parcursul ciclului. De asemenea, este mai eficienta apelarea funciei prepare () o singur dat, pentru a configura instruciunea INSERT nainte de a introduce ciclul, precum si invocarea funciei execute () o singur dat n cadrul ciclului. Astfel, se evit toate invocrile funciei prepare (), cu o singur excepie. DBI ne permite acest lucru, astfel: $dbh->do (qq{ DELETE FROM elev } ); # terge rndurile existente $sth = $dbh->prepare (qq{ INSERT elev SET nume = ? }); while (<>) # adaug rnduri noi { chomp; $sth->execute ($_); } $sth->finish(); Observai semnul ntrebrii din interogarea INSERT. Acela este cmpul de nlocuire. Cnd este invocat funcia execute (), transferai valoarea care va lua locul cmpului de nlocuire atunci cnd interogarea este trimis la server, n general, dac apelai funcia do () n cadrul unui ciclu, este mai bine s invocai prepare () anterior ciclului, respectiv execute() n interiorul acestuia. Cteva aspecte de reinut despre cmpurile de nlocuire: Nu delimitai caracterul cmp de nlocuire cu ghilimele n interiorul irului interogrii. Dac o facei, acesta nu va fi recunoscut drept cmp de nlocuire. Nu folosii metoda quote () pentru a specifica valorile cmpului de nlocuire; n caz contrar, vei obine ghilimele suplimentare n valorile pe care le inserai. ntr-un ir al unei interogri pot exista mai multe cmpuri de nlocuire, dar nu uitai s transmitei funciei execute ()un numr de valori egal cu indicatoarele pentru cmpurile de nlocuire.

318 Partea a l)-a Utilizarea interfeelor de programare ale sistemului MySQL Fiecare cmp de nlocuire trebuie s specifice o singur valoare, nu o list de valori. De exemplu, nu putei prepara i executa o instruciune ca aceasta: $stti = $dbh->prepare (qq{{ INSERT membru nume, prenume VALUES(?) }); $sth->execute ("Adams,Bill,2003-09-19"); Trebuie s procedai astfel: $sth = $dbh->prepare (qq{{ INSERT membru nume, prenume VALUES(?,?,?) }); $sth->execute ("Adams","Bill","2003 -09-19"); Pentru a specifica NULL ca valoare a cmpului de nlocuire, folosii undef. Nu ncercai s folosii un cmp de nlocuire pentru cuvintele cheie. Nu vei reui, deoarece valorile din cmpurile de nlocuire sunt prelucrate automat de funcia quote (). Cuvntul cheie va fi plasat n interogare delimitat prin ghilimele, iar interogarea va eua datorit unei erori de sintax. Pentru unele motoare de baze de date, vei obine o cretere suplimentar a performanei din adugarea cmpurilor de nlocuire, n afar de o eficien crescut a ciclurilor. Anumite motoare memoreaz n cache interogrile preparate, precum i planul generat de acestea pentru executarea eficient a interogrilor. Astfel, dac aceeai interogare este primit de server mai trziu, va putea fi refolosit fr a se genera un nou plan de execuie. Memorarea n cache a interogrilor este util mai ales pentru instruciuni SELECT complexe, deoarece generarea unui plan bun de execuie poate necesita timp. Cmpurile de nlocuire v ofer o posibilitate de localizare mai rapid a interogrii n cache, deoarece aceste cmpuri confer interogrilor un caracter mai generic dect cele construite prin nglobarea anumitor valori din coloane direct n irul de interogare. Pentru MySQL, cmpurile de nlocuire nu mbuntesc performanele n acest mod, deoarece interogrile nu sunt memorate n cache. Totui, v putei scrie propriile interogri folosind cmpurile de nlocuire; dac se ntmpl s portai un script DBI la un motor care folosete o zon cache pentru interogri, scriptul dumneavoastr se va executa mai eficient dect n absena cmpurilor de nlocuire. Cmpurile de nlocuire v permit s inserai valori ntr-un ir de interogare n momentul execuiei interogrii. Cu alte cuvinte, putei parametriza datele de intrare" ale interogrii. De asemenea, DBI furnizeaz o operaie corespunztoare la ieire, denumit asociere a parametrilor, care v permite s parametrizai datele de ieire" prin regsirea automat a valorilor coloanelor n variabile atunci cnd preluai un rnd, fr a trebui s atribuii personal valori variabilelor. S presupunem c avei o interogare care regsete numele membrilor din tabelul membru. Putei cere programului DBI s atribuie unor variabile Perl valorile coloanelor selectate. Cnd preluai un rnd, variabilele sunt actualizate automat cu valorile din coloana corespunztoare. Iat un exemplu care prezint modul de

asociere a coloanelor la variabile i apoi modalitatea de acces la acestea n cadrul ciclului de preluare: $sth = $dbh->prepare (qq{ Capitolul 7 Interfaa API pentru Perl DBI 319 SELECT nume, prenume, sufix FROM membru ORDER BY nume, prenume }); $sth->execute (); $sth->bind_col (1, \$nume); $sth->bind_col (2, \$prenume); $sth->bind_col (3, \$sufix); prin "$nume, Sprenume, $sufix\n" while $sth->fetch; Fiecare apel la funcia bind_col () trebuie s specifice un numr de coloan i o referin la variabila pe care dorii s o asociai la coloan. Numerele coloanelor ncep de la 1. bind_col() trebuie apelat dup execute {). Ca alternativ la apelurile individuale la funcia bind_col (), putei transmite toate referinele la variabile ntr-un singur apel la funcia bind_columns (): $sth = $dbh->prepare (qq{ SELECT nume, prenume, sufix FROM membru ORDER BY nume, prenume }); $sth->execute (); $sth->bind_columns (\$nume, \$prenume, \$sufix); prin "$nume, $prenume, $sufix\n" while $sth->fetch; Specificarea parametrilor de conexiune Modalitatea cea mai direct de a stabili o conexiune cu serverul este de a specifica toi parametru de conexiune atunci cnd invocai metoda connect (): $data_source = "QBI:mysqI:nume_baza_de_clate:nume_gazda"; $dbh->connect ($data_source, nume_utilizator, paro2a); Dac omitei parametrii de conexiune, DBI execut urmtoarele operaii: Dac sursa de date nu este definit sau dac este irul vid, se folosete variabila de mediu DB1_DSN. Dac numele de utilizator i parola nu sunt definite (dar nu i dac acestea sunt alctuite dintr-un ir vid), se folosesc variabilele de mediu DBI_USER i DBI_PASS. Sub Windows, dac numele de utilizator nu este definit, se folosete variabila USER. Dac omitei numele gazdei, aceasta are ca valoare prestabilit localhost. Dac specificai undef sau un ir vid pentru numele de utilizator, acesta are ca valoare prestabilit numele dumneavoastr UNIX de deschidere a sesiunii de lucru. Sub Windows, numele de utilizator prestabilit este ODBC. Dac n locul parolei specificai undef sau un ir vid, nu este trimis nici o parol. Putei specifica anumite opiuni n sursa de date prin ataarea lor la partea iniial a irului, fiecare opiune fiind precedat printr-un caracter punct i virgul. De exemplu, putei folosi opiunea mysql_read_def ault_f ile pentru a preciza un nume al cii de acces spre un fiier cu opiuni: 320 Partea a ll-a Utilizarea interfeelor da programare ale sistemului MySQL $data_source = , , "DBI:mysql:samp_db;mysql_read_default_file=/u/pauiy,my.cnf"j Cnd scriptul se execut, va citi fiierul n cutarea parametrilor de conexiune. S presupunem c /u/paul/my .cnf are urmtorul coninut: [client] host=pit-viper.snake.net , ; user=paul password=secret > Apoi, apelul la funcia connect () va ncerca s se conecteze la serverul MySQL din calcu--, latorul pitviper.snake.net i se va conecta ca utilizatorul paul cu parola secret. Dac dorii s permitei ca scriptul dumneavoastr s fie utilizat de orice persoan care dispune de un fiier cu opiuni configurat n mod corespunztor, specificai sursa de date astfel; . $daa_source = DBI:mysql:samp_db;mysql_read_default_fUe=$ENV{HOME}/.my.cnf"; $ENV{HOME} conine calea de acces spre catalogul de baz al utilizatorului care ruleaz scriptul, deci numele gazdei, numele utilizatorului si parola pe care le folosete seriptul f vor fi extrase din fiierul cu opiuni al fiecrui utilizator. Scriind un script n acest mod, l nu trebuie s nglobai literalmente parametrii de conexiune n script. De asemenea, putei folosi opiunea mysql_read_default_group, pentru a specifica un f grup din fiierul cu opiuni. Aceasta determin n mod automat citirea fiierului, jqy. cnf al utilizatorului i v permite s specificai

un grup de opiuni care va fi citit alturi de | grupul [client]. De exemplu, dac avei opiuni care sunt specifice scripturilor dum- l neavoastr DBI, le putei meniona ntr-un grup [dbi], dup care folosii o valoare al sursei de date ca aceasta: $data_source = ' ><. "DBI:mysql:samp_db;mysql_read_default_group=dbi"; < mysql_read_default_file i mysql_read_default_gpoup necesit MySQL 3.22.10 sau gj versiune mai recent, precum iDBD::mysql 1.21.06 sau mai recent. Pentru mai multe| detalii privind opiunile pentru specificarea irului surs de date, vezi Anexa G. Pentm| mai multe informaii despre .formatul fiierelor cu opiuni MySQL, vezi Anexa Referin de program MySQL". *>* Utilizarea unui fiier cu opiuni nu v mpiedic s specificai parametrii de conexiune | n apelul la funcia connect () (de exemplu, dac dorii ca scriptul s, se conecteze ca un| anumit utilizator). Orice valpare explicit a numelui gazdei, a numelui de utilizator saiif a parolei specificate n apelul la funcia connect () va redefini parametrii de conexiv aflai n fiierul cu opiuni. t)e exemplu, putei dori ca scriptui dumneavoastr sa analizeze opiunile - -host, - -uier i - -password din linia de comand i s foloseasc! valori, dac sunt date, cu prioritate fa de orice valoare din fiierul cu ojpiuni. fapt este util, deoarece este modalitatea standard de comportare a clienilor MySQL. < atare, scripturile dumneavoastr DBI vor manifesta aceeai comportarq.'! Pentru celelalte scripturi n linie de comand pe care le vom crea n acest capitol, folosi linii de program standard pentru stabilirea! respectiv ntreruperea conexiunii, voi prezenta o singur dat aici, astfel nct s ne putem concentra asupra corpului pri cipal al fiecrui script atunci cnd l vom scrie: ;.,,. , Capitolul/ Interfaa API pentru Peri DBI 321 #! /usr/bin/perl use DBI; ' '"i;; " '" '---';i .'''"'' '"''"' use strict; "iV '' ''' '' ""' ' '*' ':' "': " analizeaz parametrii de conexiune din linia de"comanda, # daca acetia sunt dai . > -use-eteptritong; .::.:,..;,,<:;,...: .,?' $GetopH: :Lbng: :ij)norecase 0; W optinile! simt -sensibile la # dife'rerft' intre itrjircule sil mihus'c'ule " \ # toi prmet'rii prestabilii' lipse'sc ' ''* my (^host^ftame , 'iluser^ame, $f?assw)rd) = (undcf, 'undef , 'undef ); ;# <eOptibns nu pa^ sa1 permit forma -uuser_name,' ., # numai -u/useriWaMe4?0'^ <'>'"'' ' :--''i- ;- ' ."' ..... ,'- * GetOptions( # =s nseamn ca dup opiune este necesar un argument ir :: .' "host/vh=s" ' -.;.i;.'t- > . !*> \$host_na|ne .... :... ,<"uer/iN8" 'L .ii-;.- /..- ^=> \$useT*_name : # :s nseamn ca dup opiune argumentul ir este opional ,"password/p:s" => \$password or # solicit parola daca opiunea .',este specif picata' "fr valoare if '(defined (|passwp-d^ && $passwprd): ' ^ . "' ". }r. '" '' ' 1" "' " ,{ # dezcti.ve|aza rfefl^ctarea, dsr^nu interfereaz,tu,Si open (Tiy, "/dey/tty") pr die. ("Nu.poate deschide terminaj.ul\n"); system ("stty -echo < /dev/tty/"); print STDERR 'Introducei parola: "; chomp (Spassword = <TTY>); '?Hii>J ' ".vif'Wur ''., -ru^ViUi . >.>'>"> .system (?sjtty^eokp;<\ (fdv/ttyi')^n!i ,, ; t .prin surs* 4* datei my ($dsn) = "DBI:mysql:samp_db"; _ .. j .. Continuare

322 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare $dsn .= ":hostname=$host_name" if $host_name; $dsn .= ";mysql_read_default_file=$ENV{HOME}/.my.cnf"; # conectare la server

my (%attr) = ( RaiseError => 1 ); my ($dbh) = DBI->connect ($dsn, $user_name, $password, \%attr); Acest program iniializeaz DBI, caut parametrii de conexiune n linia de comand i apoi stabilete conexiunea cu serverul MySQL folosind parametri din linia de comand sau parametri din fiierul ~/my. cnf al utilizatorului care ruleaz scriptul. n cazul n care configurai fiierul my. cnf n catalogul dumneavoastr de baz, nu va trebui s introducei nici un parametru de conexiune atunci cnd rulai scripturile. (Nu uitai s configurai modul astfel nct nimeni altcineva s nu mai poat citi fiierul. Pentru instruciuni, consultai Anexa E.) Partea final a scripturilor noastre va fi de asemenea identic pentru toate scripturile; pur si simplu se suspend conexiunea si se ncheie execuia programului: $dbh->disconnect (); exit (0); Cnd vom ajunge la seciunea de programare Web, Utilizarea DBI n aplicaii Web", vom modifica puin programul de configurare a conexiunii, dar ideea de baz va rmne aceeai. Depanare Cnd dorii s depanai un script DBI care nu funcioneaz adecvat, de obicei se folosesc dou tehnici, fie separat, fie n tandem. Mai nti, putei mprtia" instruciuni de afiare pe tot cuprinsul scriptului dumneavoastr. Astfel, v putei ajusta datele de ieire ale procesului de depanare conform preferinelor, dar trebuie s adugai instruciunile manual, n al doilea rnd, putei folosi funcionalitile de urmrire ale programului DBI. Aceasta este o opiune mai general, dar mai sistematic, i care intr n aciune imediat dup ce ai activat-o. De asemenea, v prezint informaii despre funcionarea driverului pe care nu le putei obine altfel. Depanarea folosind instruciuni de afiare O ntrebare frecvent n lista de coresponden pentru MySQL este urmtoarea: Am o interogare care funcioneaz bine cnd o execut n mysql, dar care nu funcioneaz din scriptul DBI. Cum vine asta?" Este ceva obinuit ca un script DBI s emit o alt interogare dect cea dorit de operator. Dac afiai o interogare nainte de a o executa, poate vei fi surprins s vedei ceea ce trimitei efectiv serverului. S presupunem c o interogare pe care o tastai se prezint astfel (fr caracterul punct i virgul de terminare): INSERT membru (nume,prenume)data_expirare) VALUES("Vasile",'Cornel","2002-6-3") Apoi, ncercai acelai lucru ntr-un script DBI: Capitolul 7 Interfaa API pentru Perl DBI 323 $nume = "Vasile"; $prenume = "Cornel"; $data_expirare = "2002-6-3"; $query = qq{ INSERT membru (nume,prenume,data_expirare) VALUES($nume,$prenume,$data_expirare) }; $rows =dbh->do ($query); Interogarea nu va funciona, chiar dac este aceeai cu prima. Dar este oare? ncercai s o afiai: prin "$query\n"; lata rezultatul: INSERT membru (nume,prenume,data_expirare) VALUES(Vasile,Cornel,2002-6-3) Din aceste date de ieire, putei observa c ai uitat s scriei ghilimelele n jurul valorilor din coloane n lista VALUES (). Modalitatea corect de specificare a interogrii este aceasta: $nume = $dbh->quote {"Vasile"); $prenume = $dbh->quote ("Cornel"); $data_expirare = $dbh->quote ("2002-6-3"); $query = qq{ INSERT membru (nume,prenume,data_expirare) VALUES($nume,$prenume,$data_expirare) }; Alternativ, v putei specifica interogarea folosind cmpuri de nlocuire i transferai direct metodei do () valorile ce urmeaz a fi inserate: $nume = "Vasile"; Sprenume = "Cornel"; $data_expirare = "2002-6-3"; $query = qq{ INSERT membru (nume,prenume,data_expirare) VALUES(?,?,?) }; $rows =dbh->do ($query, undef, $nume, $prenume, $data_expirare); Din pcate, atunci cnd procedai astfel, nu putei vedea care este aspectul interogrii complete folosind o instruciune de afiare, deoarece valorile din cmpurile de nlocuire sunt evaluate numai cnd invocai metoda do(). Cnd folosii cmpuri de nlocuire,

urmrirea poate fi o metod de depanare mai util. 324 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Depanarea prin urmrire Putei cere programului DBI s genereze informaii de urmrire (depanare) atunci cnd ncercai s determinai motivul pentru care un script nu funcioneaz corect. Nivelurile de urmrire variaz de la O (inexistent) la 9 (aport maxim de informaii), n general, nivelurile de urmrire l i 2 sunt cele mai utile. O urmrire de nivel 2 v prezint textele interogrilor pe care le executai (inclusiv rezultatul inseriei valorilor n cmpurile de nlocuire), rezultatul apelurilor la funcia quote () etc. Aceasta poate fi de mare ajutor n depistarea unei probleme. Putei controla nivelul de urmrire din interiorul scripturilor individuale folosind metoda trace () sau putei configura variabila de mediu DBI_TRACE, pentru a influena urmrirea tuturor scripturilor DBI pe care le rulai. Pentru a folosi apelul la funcia trace(), transferai un argument nivel de urmrire i, opional, un nume de fiier. Dac nu specificai nici un nume de fiier, toate datele de ieire ale procesului de urmrire ajung la STDERR; n caz contrar, ajung la fiierul denumit. Cteva exemple: DBI ->trace (1) Urmrire de nivel l cu ieire n STDERR DBI->trace (2, "trace.out") Urmrire de nivel 2 cu ieire n trace.out" DBI ->trace (0) Dezactivarea datelor de ieire ale urmririi Cnd funcia este invocat sub forma DBI->trace(), sunt urmrite toate operaiile DBI. Pentru o abordare mai detaliat, putei activa urmrirea la nivelul variabilei de manipulare individuale. Acest lucru este util cnd tii bine unde se afl o anumit problem n scriptul dumneavoastr si nu dorii s parcurgei datele de ieire ale procesului de urmrire, pentru a afla tot ceea ce s-a produs pn n momentul respectiv. De exemplu, dac avei probleme cu o anumit interogare SELECT, putei urmri variabila de manipulare pentru instruciuni asociat interogrii: $sth-> $dbh->prepare (qq{ SELECT ... }); $sth->trace (1); # creeaz variabila de # manipulare a instruciunii # activeaz urmrirea # instruciunii $sth->execute (); Dac specificai un argument nume de fiier n orice apel la funcia trace(), indiferent dac pentru ntreg scriptul DBI sau pentru o variabil de manipulare individual, toate datele de ieire ale procesului de urmrire ajung la fiierul respectiv. n vederea activrii urmririi la nivel global pentru toate scripturile DBI pe care le rulai, configurai variabila de mediu DBI_TRACE din interpretorul dumneavoastr. Sintaxa necesar depinde de interpretorul pe care l folosii: % setenv DBI_TRACE valoare Pentru csh, tcsh $ DBl_THACE=valoare Pentru sh,ksh,bash $ export DBIJTRACE C:\> set DBI TRACE=valoare Pentru Windows Capitolul? Interfaa API pentru Perl DBI 325 Formatul pentru valoare este acelai pentru toate interpretoarele: un numr n pentru activarea urmririi la nivelul n cu ieire n STDERR; un nume de fiier pentru activarea urmririi de nivel 2 cu ieire n fiierul denumit, respectiv n=nume_f isier pentru activarea urmririi de nivel n cu ieire n fiierul denumit. Iat cteva exemple care folosesc sintaxa csh: % setenv DBI_TRACE 1 Urmrire de nivel l cu ieire n STDERR % setenv DBI_TRACE 1=trace.out Urmrire de nivel l n trace.out" % setenv DBI_TRACE trace.out Urmrire de nivel 2 n trace.out" Dac activai urmrirea ntr-un fiier din interpreter, nu uitai s o dezactivai dup ce ai rezolvat problema. Datele de ieire de la depanare sunt anexate la fiierul de urmrire fr a-1 suprascrie, deci fiierul poate deveni foarte mare dac nu suntei atent. Este total contraindicat s definii DBI_TRACE ntr-un fiier de pornire din interpretor, precum . cshrc, .login sau .profile! Sub UNIX, putei dezactiva urmrirea folosind oricare din urmtoarele comenzi (sintax csh): % setenv DBIJTRACE O % unsetenv DBI_TRACE Pentru sh, ksh sau bash procedai astfel: $ DBI_TRACE=0 $ export DBIJTRACE n Windows, putei dezactiva caracteristica de urmrire folosind oricare din aceste programe: C:\> unset DBI_TRACE C:\> set DBI_TRACE=0 Utilizarea metadatelor aferente setului de rezultate Putei folosi DBI pentru a obine accesul la metadatele setului de rezultate, adic la informaii descriptive despre rndurile selectate de o interogare. Pentru a obine aceste informaii, obinei accesul la atributele variabilei de

manipulare pentru instruciuni asociat cu interogarea care a generat setul de rezultate. Unele din aceste atribute sunt furnizate ca atribute DBI standard care sunt disponibile pentru toate driverele de baze de date (precum NUM_OF_/IELDS, numrul coloanelor din setul de rezultate). Altele, specifice sistemului MySQL, sunt furnizate de DBD::mysql, driverul MySQL pentru DBI. Aceste atribute, precum mysql_max_length, care indic limea maxim a valorilor din fiecare coloan, nu sunt aplicabile altor motoare de baze de date. n msura n care folosii oricare dintre atributele specifice MySQL, riscai ca scripturile dumneavoastr s devin neportabile pe alte baze de date. Pe de alt parte, pot facilita obinerea informaiilor pe care le dorii. Trebuie s solicitai metadatele la momentul adecvat, n general, atributele setului de rezultate nu sunt disponibile pentru 6 instruciune SELECT dect dup ce ai invocat p repare () si execute(). n plus, atributele pot deveni invalide dup ce invocai f inish(). S vedem cum se poate folosi unul din atributele metadatelor MySQL, i anume mysql_max_length, n conjuncie cu atributul de nivel DBI NAME, care conine numele coloanelor din interogare. Putem combina informaiile furnizate de aceste atribute pentru a scrie un script box_out, care genereaz date de ieire ale interogrilor SELECT n 326 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL acelai stil ncasetat" pe care l obinei cnd rulai programul client mysql n mod interactiv. Corpul principal al scriptului box_out este urmtorul (putei nlocui instruciunea SELECT cu oricare alta; rutinele de scriere a datelor de ieire sunt independente de interogarea n sine): my ($sth) = $dbh->prepare (qq{ SELECT nume, prenume, ora, stat FROM preedinte ORDER BY nume, prenume $sth->execute (); # atributele trebuie sa fie disponibile # dup acest apel # limile maxime efective ale valorilor coloanelor din # setul de rezultate my (@wid) = @{$sth->{mysql_max_length}}; # numr de coloane my ($ncols) = scalar (@wid); # ajusteaz limile coloanelor in cazul in care antetele # coloanelor sunt mai late dect valorile datelor for (my $i = 0; $i < $ncols; $i++) { my ($name_wid) = length ($sth->{NAME}->[$i]); $wid[$i] = $name_wid if $wid[$i] < $name_wid; # afieaz datele de ieire print_dashes (\$wid, $ncols); print_row ($sth->{NAME>, \ewid, Sncols); print_dashes (\wid, $ncols); while (my $ary_ref = $sth->fetch) { print_row ($ary_ref, \@wid, Sncols); # rnd de liniute # antete de coloana # rnd de liniute # valori date din rnd # rnd de liniute print_dashes (\@wid, $ncols); $sth->finish(); Dup ce interogarea a fost iniiat cu execute(), putem prelua metadatele de care avem nevoie. $sth->{NAME} i $sth->{mysql_max_length} ofer numele coloanelor i limea maxim a valorilor din fiecare coloan. Valoarea fiecrui atribut este o referin la un tablou care conine cte o valoare pentru fiecare coloan a setului de rezultate, n ordinea n care sunt specificate coloanele n interogare. Capitolul 7 Interfaa API pentru Perl DBI 327 Calculele rmase sunt foarte asemntoare celor folosite pentru programul clients prezentat n capitolul 6. De exemplu, pentru a preveni alinierea eronat a datelor de ieire, vom ajusta limile coloanelor de jos n sus, dac numele unei coloane este mai lat dect oricare din valorile datelor din coloan. Funciile de ieire, printjdashes() i print_row(), sunt scrise dup cum urmeaz. i acestea sunt similare programului corespunztor din clients: sub print_dashes my ($wid_ary_ref ) = shift; my ($cols) = shift; # limile coloanelor # numrul de coloane prin "+"; for (my $i = 0; $i < $cols; $i++)

{ prin "-"x ($wid_ary_ref->[$i]+2) . "+"; } prin "\n"; # afieaz un rnd de date. # (nu aliniaz la dreapta coloanele numerice) sub print_row { my ($val_ary_ref) = shift; my ($wid_ary_ref) = shift; my ($cols) = shift; # valorile din coloane # limile coloanelor # numrul de coloane prin "l1; for (my $i = 0; $i $cols; $i++) printf " %-*s l", $wid_ary_ref->t$i], defined ($val_ary_ref->[$i] ? $val_ary_ref->[$i] prin "\n"; Datele de ieire ale scriptului box_out sunt urmtoarele: " NULL "; nume prenume oras stat Adams John Braintree MA Adams John Quincy Braintree MA Arthur Chester A. Fairfield VT Buchanan James Mercersburg PA Bush George W. Milton MA 328 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Urmtorul nostru script folosete metadatele referitoare la coloane pentru a produce date de ieire ntr-un alt format. Acest script, showjnember, v permite s examinai rapid datele privind membrii Ligii istorice, fr a introduce nici o interogare. Dac se d numele unui membru, intrarea selectat este afiat astfel: % show member artel nume: prenume: sufix: data_expirare: email: strada: ora: stat: cod_postal: telefon: interese: membru id: Artel Mike 2003-04-16 ntike_arteli>venus. org 4264 Lovering Rd. Miami FL 12777 075-961-0712 drepturile civile,nvmnt,rzboiul revoluionar 63 De asemenea, putei invoca showjnembers folosind identificatorul de membru sau utiliznd un model care s corespund mai multor nume de familie. Comenzile urmtoare afieaz intrarea pentru numrul 23 si intrrile pentru membrii ale cror nume de familie ncepe cu litera C: % showjnember 23 % showjnember c% Corpul principal al scriptului sfiowjnember este prezentat mai jos. Scriptul folosete atributul NAME pentru a determina etichetele de utilizat pentru fiecare rnd al datelor de ieire, respectiv atributul NUM_OF_FIELDS pentru a afla numrul de coloane pe care l conine setul de rezultate: my ($count) =0; # numrul de intrri afiate pana acum my (Slabei) = (); # tabloul cu etichetele de coloana my ($label_wid) =0; while (@ARGV) # ruleaz interogarea pentru fiecare argument # din linia de comanda

my ($arg) = shift (SARGV); my ($sth, $clause, Saddress); # in mod prestabilit se caut dup nume; caut dup # identificator daca argumentul este numeric Sclause = "nume LIKE " . $dbh->quote ($arg); Sclause = "membru_id = " . $dbh->quote ($arg) if $arg=- /"\d/; # emite interogarea Capitolul? Interfaa API pentru Perl DBI 329 $sth = $dbh->prepare (qq{ SELECT * FROM membru WHERE Sclause ' ORDER BY nume, prenume $sth->execute (); # obine numele coloanelor de utilizat in etichete si determina # limea maxima a numelor coloanelor pentru formatare (facei # aceasta numai la prima parcurgere a ciclului) if ($label_wid ==0) @label = 3{$sth->{NAME}}; foreach my Slabei (Slabei) $label_wid = length (Slabei) if $label_wid < length (Slabei) # citete si afieaz rezultatele interogrii, apoi face curenie while (my @ary = $sth-fetchrow_array ()) { # afieaz linie noua inainte de intrarea a doua # si de intrrile ulterioare print "\n" if ++$count > 1; foreach (my $i = 0; $i < $sth->{NUM_OF_FIELDS}; $i++) { # afieaz eticheta si valoarea, daca exista printf "%-*s", $label_wid+1 , $label[$i] . ":"; prin " " . $ary[$i] if $ary[$i]; prin "\n"; $sth->finish (); } . Rolul scriptului show_member este de a prezenta ntregul coninut al unei intrri, indiferent care sunt cmpurile. Folosind SELECT * pentru a regsi toate coloanele i atributul NAME pentru a afla care sunt acestea, scriptul respectiv va funciona fr modificri, chiar dac sunt adugate sau eliminate coloane din tabelul membru. Dac nu dorii dect s aflai care sunt coloanele pe care le conine un tabel, fr a regsi vreun rnd, putei emite aceast interogare: SELECT * .FROM nume tabel WHERE 1=0 330 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL

Dup ce invocai prepare () i execute() ca de obicei, putei obine numele coloanelor din @{$sth->{NAME}}. Reinei, totui, c dei acest mic truc de utilizare a unei interogri vide" funcioneaz pentru MySQL, nu este portabil i nu se poate aplica tuturor motoarelor de baze de date. Pentru mai multe informaii privind atributele furnizate de DBI i de DBD::mysql, consultai Anexa G. Este la latitudinea dumneavoastr s determinai dac dorii s obinei un maxim de portabilitate evitnd atributele specifice sistemului MySQL sau s beneficiai de ele, cu preul reducerii portabilitii. Utilizarea DBI Pn acum ai vzut un numr de concepte implicate n programarea DBI, deci s trecem la unele din lucrurile pe care doream s le putem face n ceea ce privete baza noastr de date demonstrativ. Scopurile noastre au fost schiate la nceput n capitolul l, Introducere n MySQL i SQL". Cele care vor fi abordate prin scrierea scripturilor DBI din capitolul de fa sunt enumerate aici. Pentru proiectul de eviden a rezultatelor colare, dorim s putem regsi punctaje pentru orice chestionar sau test dat. Pentru Liga istoric, dorim s putem executa urmtoarele operaii: Generarea catalogului membrilor n diferite formate. Dorim o list numai cu nume, care va fi utilizat n programul editat cu ocazia banchetului anual, ntr-un format care se poate folosi pentru generarea catalogului tiprit. Identificarea acelor membri ai Ligii care trebuie s-i plteasc n curnd cotizaia, urmat de trimiterea unui mesaj e-mail pe adresa acestora, pentru a-i anuna. Editarea intrrilor aferente membrilor. (La urma urmelor, va trebui s le actualizm datele de expirare dup ce acetia i achit cotizaiile.) Identificarea membrilor cu interese comune. Publicarea catalogului pe Internet. Pentru unele din aceste sarcini, vom scrie scripturi care ruleaz de la linia de comand. Pentru celelalte, vom crea scripturi n seciunea urmtoare, Utilizarea DBI n aplicaiile Web", pe care le putei folosi n conjuncie cu serverul dumneavoastr de Web. La sfritul capitolului, vom mai rmne cu un numr de sarcini nendeplinite. Le vom rezolva i pe acestea n capitolul 8, Interfaa API pentru PHP".

Generarea catalogului Ligii istorice Unul dintre scopurile noastre este de a genera informaii din catalogul Ligii istorice n diferite formate. Cel mai simplu format pe care l vom genera este o list cu numele membrilor pentru programul banchetului anual. Aceasta poate fi un listing n format text simplu. Lista va deveni o parte a documentului mai mare folosit pentru a crea programul, deci tot ceea ne trebuie este o entitate care poate fi lipit" n documentul respectiv. Pentru catalogul n format acceptabil pentru tipar, este necesar o reprezentare mai adecvat dect aceea n format text simplu, deoarece dorim un text cu o formatre estetic. O opiune rezonabil aici este RTF (Rich Text Format), un format creat de Microsoft i Capitolul 7 Interfaa API pentru Perl DBI 331 acceptat de numeroase procesoare de text. Word este un asemenea program, desigur, dar RTF este acceptat i de multe alte programe, precum WordPerfect i AppleWorks. Diferitele procesoare de text accept RTF n moduri diferite, dar vom folosi un subset elementar al specificaiei RTF complete, care trebuie s fie acceptat de orice procesor de texte care prezint chiar i cel mai redus nivel posibil de compatibilitate cu RTF. Procedurile pentru generarea formatelor de catalog de tip list pentru banchet, respectiv RTF, sunt n esen aceleai: emitei o interogare pentru regsirea intrrilor si apoi rulai un ciclu care preia si formateaz fiecare intrare. Datorit acestei asemnri fundamentale, ar fi interesant s evitm scrierea a dou scripturi diferite, n acest sens, s scriem un singur script, intitulat gen_cat, care poate genera date de ieire din catalog n diferite formate. Putem structura scriptul dup cum urmeaz: 1. nainte de a scrie coninutul intrrilor, efectuai toate iniializrile necesare pentru formatul datelor de ieire. Pentru lista membrilor care se va insera n programul pentru banchet nu este necesar nici o iniializare special, dar va trebui s scriem un limbaj de control iniial pentru versiunea RTF. 2. Preluai i afiai fiecare intrare, formatat adecvat pentru tipul datelor de ieire dorit. 3. Dup ce toate intrrile au fost prelucrate, efectuai toate operaiile necesare de curenie i de terminare a programului. Din nou, pentru lista de la banchet nu este nevoie de operaii de manipulare speciale, dar pentru versiunea RTF este necesar un limbaj de control al nchiderii. n viitor, vom folosi acest script pentru a scrie date de ieire n alte formate, deci l vom face extensibil prin configurarea unei cutii de distribuie" - o combinaie cu un element pentru fiecare format al datelor de ieire. Fiecare element specific funcii care genereaz date de ieire n mod adecvat pentru un format dat: o funcie de iniializare, o funcie de scriere a intrrilor i o funcie de curenie: # cutie de distribuie care conine funcii de formatare # pentru fiecare format al datelor de ieire my (%cutie_distributie) = "banquet" => # funcii pentru lista de banchet "init" => undef, # nu este necesara nici # o iniializare "entry" => \&format_banquet_entry, "cleanup" => undef # curenia nu este necesara }, "rtf" => { "init" => \&rtf_init, "entry" => V&format_rtf_entry, "cleanup" => \&rtf_cleanup } # funcii pentru formatul RTF

332 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Fiecare element din cutia de distribuie include un nume de format drept cheie (n cazul nostru banquet" i rtf"). Vom scrie scriptul astfel nct dumneavoastr s specificai numai formatul dorit n linia de comand atunci cnd l rulai: % gen_cat banquet % gen_cat rtf Prin configurarea unei cutii de distribuie" n acest mod, putem aduga cu uurin funcionalitatea unui nou format: 1. Scriei trei funcii de formatare. 2. Adugai la cutia de distribuie un element nou, care indic spre aceste funcii.

3. Pentru a produce date de ieire n noul format, invocai gen_cat si specificai numele formatului n linia de comand. Programul pentru selectarea intrrii adecvate din cutia de distribuie" n conformitate cu primul argument din linia de comand este prezentat mai jos i se bazeaz pe faptul c numele formatelor datelor de ieire reprezint cheile din hash-ul %cutie_distribu-tie. Dac n cutia de distribuie nu exista o asemenea cheie, formatul este incorect. Nu este necesar codarea hard a numelor formatelor n program; dac la cutia de distribuie este adugat un format nou, acesta este detectat automat. Dac n linia de comand nu este specificat nici un nume de format sau este precizat un format incorect, scriptul produce un mesaj de eroare si afieaz o list a numelor permise: # verifica daca a fost specificat un argument in linia de comanda @ARGV = = 1 or die "Utilizare : gen_cat tip_format \nFormate permise:" . join (" ", sort (keys (%cutie_distributie))) . "\n"; # determina intrarea adecvata din cutia de distribuie din # argumentul liniei de comanda; daca nu este gsita nici o # intrare, tipul de format a fost incorect my ($func_hashref ) = $cutie_distributie{$ARGV[0]}; defined ($func_hashref ) or die "Format necunoscut: $ARGV[0] \nFormate permise: " . join (" ", sort (keys (%cutie_distributie)) ) . "\n"; Dac n linia de comand este specificat un nume de format corect, programul precedent configureaz funcia $f unc_hashref . Valoarea sa va fi o referin spre hash, care indic spre funciile de scriere a datelor de ieire pentru formatul selectat. Apoi, putem rula interogarea de selecie a intrrii. Apoi, invocm funcia de iniializare, prelum i afim intrrile i invocm funcia de curenie: # emite interogarea my ($sth) = $dbh->prepare (qq{ SELECT * FROM membru ORDER BY nume, prenume $sth->execute Capitolul 7 Interfaa API pentru Peri DBI 333 #invoca funcia de iniializare, daca exista vreuna &{$func_hashref->{init}} if defined ($func_hashref ->{init}) ; # preia si afieaz intrrile, daca exista vreo funcie de # formatare de datelor de intrare if (defined ($func_hashref ->{entry}) ) { while (my $entry_ref = $sth->fetchrow_hashref ("NAME_lc")) { # transmite referina la intrare funciei de formatare &{$f uncjiashref ->{entry}} ($entry_ref ) ; $sth->finish (); # invoca funcia de curenie, daca exista vreuna &{$func_hashref ->{cleanup}} if defined ($func_hashref ->{cleanup}) ; Ciclul de preluare a intrrilor folosete fetchrowjiashref () dintr-un anumit motiv. Dac ciclul prelua un tablou, funciile de formatare ar fi trebuit s cunoasc ordinea coloanelor. O putei determina prin accesul la atributul $sth->{NAME} (care conine numele coloanelor n ordinea n care au fost returnate), dar de ce s v obosii? Folosind o referin la hash, funciile de formatare pot denumi exact coloanele de care au nevoie folosind construcia $entry_ref ->(nume_coloana). Acest procedeu nu este deosebit de eficient, dar este uor si poate fi folosit pentru orice format pe care dorim s-1 generm, deoarece tim c toate cmpurile de care avem nevoie se gsesc n hash. Tot ce ne rmne de fcut este s scriem funciile pentru fiecare format al datelor de ieire (adic pentru funciile denumite de intrrile din cutia de distribuie). Generarea listei cu membri pentru programul banchetului anual Pentru acest format al datelor de ieire, avem nevoie pur si simplu de numele membrilor. Nu sunt necesare apeluri la funcii de iniializare sau curenie. Ne trebuie numai o funcie de formatare a intrrii: sub format_banquet_entry { my ($entry_ref) = shift; my ($nume); $nume = $entry_ref ->{prenume} . " " . $entry_ref ->{nume}; if ($entry_ref ->{sufix}) # numele are un sufix { # fr virgule pentru sufixele I, II, III etc. $nume .= "," unless $entry_ref ->{sufix} =- /A[IVX]+$/; Continuare

334 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare $nume .= " " . $entry_ref->{sufix} if $entry_ref->{sufix}; print "$nume\n"; Argumentul funciei format_banquet_entry() este o referin la hash-ul valorilor din coloane aferente unui rnd. Funcia altur numele i prenumele, plus un eventual sufix. Poanta este c sufixe precum Jr." sau Sr." trebuie precedate de o virgul i de un spaiu, n timp ce sufixele de forma II" sau III" trebuie s fie precedate numai de un spaiu: Michael Alvis IV Clarence Elgar, Jr. Bill Matthews, Sr. Mark York II Deoarece literele I, V i X acoper toate generaiile, de la prima la a 39-a1, putem determina dac vom insera sau nu o virgul folosind urmtorul test: $nume .= "," unless $hash_ref->{sufix} =- /"[IVX]+$/; Programul aferent funciei format_banquet_entry() care altur numele si prenumele ne va fi necesar i pentru versiunea RTF a catalogului. Totui, n loc de a duplica programul respectiv n funcia f ormat_rtf_entry (), l vom nghesui" ntr-o funcie: sub format_name my ($entry_ref) = shift; my ($nume); $nume = $entry_ref->{prenume} . " ' . $entry_ref->{nume}; if ($entry_ref->{sufix}) # numele are un sufix # fr virgule pentru sufixele I, II, III etc. $nume .= "," unless $entry_ref->{sufix} =- r[IVX]+$/; $nume .= " " . $entry_ref->{sufix} if $entry_ref->{sufix}; return "$nume"; Prin inseria n funcia format_name() a programului care determin irul numelui, funcia f ormat_banquet_entry() se reduce aproape la zero: sub format_banquet_entry printf "%s\n", format_name ($_[0]); 1 Pentru c echivalentul n cifre romane al numrului 40 este XL. - N.T. Capitolul 7 Interfaa API pentru Perl DBI 335 Generarea catalogului n format tiprit Generarea versiunii RTF a catalogului este puin mai complicat dect generarea listei cu membri pentru programul editat cu ocazia banchetului. Un motiv ar fi acela c trebuie s afim mai multe informaii din fiecare intrare. Un al doilea motiv: este necesar inseria unui limbaj de control RTF la fiecare intrare, pentru a obine efectele dorite. Un cadru minimal pentru un document RTF se prezint astfel: {\rtfO {\fonttbl {\fO Times;}} \plain \fO \fs24 ...coninutul documentului se insereaz aici... } Documentul ncepe si se termin cu paranteze acolade. Cuvintele cheie RTF ncep cu un backslash, iar primul cuvnt cheie al documentului trebuie s fie \rtfn, unde n este numrul de versiune al specificaiei RTF creia i corespunde documentul. Pentru exemplul nostru, versiunea O este suficient.

n cadrul documentului, specificm o tabel de fonturi pentru a indica fontul care se va folosi la intrri. Informaiile din tabela cu fonturi sunt prezentate ntr-un grup alctuit din paranteze acolade, care includ un cuvnt cheie iniial \fonttbl i unele informaii despre fonturi. Tabela de fonturi prezentat n cadru definete numrul de font O ca fiiiyi n Times. (Avem nevoie de un singur font, dar putei folosi mai multe dac dorii ca documentul s aib un aspect mai estetic.) Urmtoarele cteva directive configureaz stilul de formatare prestabilit: \plain selecteaz formatul simplu, \f O selecteaz fontul O (pe care 1-am definit ca Times n tabela cu fonturi), iar \fs24 configureaz dimensiunea fontului la 12 puncte (numrul de dup f s indic dimensiunea n jumti de punct). Nu este necesar s configurai margini de pagin; majoritatea procesoarelor de texte vor furniza valori prestabilite rezonabile. Pentru a alege o abordare foarte simpl, putem afia fiecare intrare ca o serie de linii, cu o etichet pe fiecare linie. Dac informaiile corespunztoare unei anumite linii din datele de ieire lipsesc, linia este omis. (De exemplu, linia Email:" nu este afiat pentru membrii care nu dispun de adres de e-mail.) Unele linii (precum linia Adresa:") sunt alctuite din informaii plasate n mai multe coloane (strad, ora, stat, cod potal), astfel nct scriptul s poat manevra mai multe combinaii de valori care lipsesc. Iat un exemplu al formatului datelor de ieire pe care l vom folosi: Nune: Mike Artel Adresa: 4264 Levering Rd., Miami FL, 12777 Telefon: 075-961-0712 Email: mike_artel@venus.org Interese: Drepturi civile,Educaie,Rzboiul revoluionar Pentru intrarea formatat prezentat mai sus, reprezentarea RTF este urmtoarea: \b Nume: Mike Artel\bO\par Adresa: 4264 Lovering Rd., Miami FL, 12777\par Telefon: 075-961 -0712\par Email: mike_artel@venus.org\par Interese: Drepturi civile,Educaie,Rzboiul revoluionar\par

336 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Pentru ca linia care conine numele s fie scris cu aldine, delimitati-o <:u \b (urmat de un spaiu) pentru activarea scrierii cu aldine, respectiv \ bO pentru a dezactiva aceast caracteristic. Fiecare linie conine un indicator de paragraf (\par) la sfrit, pentru a indica procesorului de texte s se deplaseze pe linia urmtoare. Nu este foarte complicat. Funcia de iniializare insereaz limbajul de control RTF iniial (observai cele dou backslash-uri pentru a obine un singur backslash n datele de ieire): sub rtf_init print "{\\rtfO\n"; print "{\\fonttbl {\\fO Times;}}\n;" print "\\plain \\fO \\fs24\n'; Similar, funcia de curenie insereaz limbajul de control final (nu c-ar fi din cale-afar de mult!): sub rtf_cleanup prin "}\n"; Adevrata munc este la formatarea intrrii, dar chiar si aceast operaie este relativ simpl. Principalele complicaii apar la formatarea irului de adrese si la determinarea liniilor de ieire care trebuie afiate: sub format_rtf_entry my ($entry_ref) = shift; my ($adresa); printf "\\b Nume: %s\\bO\\par\n", formatjiame ($entry_ref); Sadresa = " "; Sadresa .= $entry_ref->{strada} if $entry_ref->{strada}; $adresa .= ", " . $entry_ref->{oras} if $entry_ref->{oras}; $adresa .=",". $entry_ref->{stat} if $entry_ref->{stat}; Sadresa .= ", " . $entry_ref->{cod} if $entry_ref->{cod}; prin "Adresa: $adresa\\par\n" if $adresa; prin "Telefon: $entry_ref->{telefon}\\par\n" if $entry_ref->{telefon}};

prin "Email: $entry_ref->{email}\\par\n" if $entry_ref->{email}}; prin "Interese: $entry_ref->{interese}\\par\n" if $entry_ref->{interese}}; prin "\\par\n"; Desigur, nu suntei obligat s folosii acest stil particular de formatare. Putei schimba J modul de afiare al oricruia dintre cmpuri, astfel nct s fie posibil modificarea stilu- f lui catalogului dumneavoastr tiprit aproape dup dorin, prin simpla modificare al funciei format_rtf_entry(). innd cont de forma original a catalogului (documenta creat cu procesorul de texte), nu este ceva chiar att de simplu! Capitolul 7 Interfaa API pentru Perl DBI 337 Scriptul gen_cat este acum complet. Putem genera catalogul n oricare din formatele de ieire, prin rularea unor comenzi precum aceasta: % gen_cat banquet > nume.txt % gen_cat rtf > catalog.rtf Sub Windows, pot rula gen_cat, iar fiierele sunt gata pentru a fi utilizate din interiorul unui procesor de texte bazat pe Windows. Sub UNIX, a putea rula aceste comenzi si apoi mi pot trimite mie personal fiierele de ieire ca fiiere ataate la mesajele de e-mail, astfel nct s le pot prelua din calculatorul meu Macintosh i apoi s le ncarc ntr-un procesor de texte. Se ntmpl s folosesc programul de post mutt, care permite specificarea fiierelor ataate n linia de comand, folosind opiunea -a. mi pot trimite mie nsumi un mesaj cu ambele fiiere ataate, dup cum urmeaz: % mutt -a nune.txt -a catalog.rtf paul@snake.net i alte programe de pot pot permite crearea fiierelor ataate. Alternativ, fiierele pot fi transferate i prin alte metode, cum ar fi FTP. n orice caz, dup ce fiierele au ajuns acolo unde dorim, citirea listei numelor i inseria sa n documentul care conine programul pentru banchetul anual, respectiv citirea fiierului RTF n orice procesor de texte care nelege formatul RTF, constituie operaii simple. DBI a facilitat extragerea informaiilor dorite din MySQL, iar funcionalitii de prelucrare a textelor din Perl au simplificat inseria acelor informaii n formatul pe care dorim s-1 vedem. MySQL nu furnizeaz nici o modalitate specific de formatare a datelor de ieire, dar aceasta nu conteaz, datorit uurinei cu care se pot integra caracteristicile sistemului MySQL de manipulare a bazelor de date ntr-un limbaj ca Perl, care dispune de excelente posibiliti de manipulare a textelor. l * Expedierea ntiinrilor pentru plata cotizaiilor Cnd catalogul Ligii istorice este pstrat ca document creat cu un procesor de texte, determinarea membrilor care trebuie anunai c trebuie s-i achite cotizaiile este o activitate consumatoare de timp si expus la erori. Acum, cnd avem informaiile ntr-o baz de date, s vedem cum putem automatiza puin procesul de ntiinare pentru plata cotizaiilor. Dorim s-i identificm prin e-mail pe membrii care trebuie s-i achite cotizaiile, astfel nct s nu mai fie necesar s-i contactm telefonic sau prin pota clasic. Ceea ce trebuie s facem este s determinm membrii care sunt obligai s-i achite cotizaia ntr-un anumit interval de timp. Interogarea pentru aceast operaie necesit calculul unei date, ceea ce este relativ simplu: SELECT ... FROM membru WHERE data_expirare < DATE_ADD(CURRENT_DATE, INTERVAL limita DAY) limita indic marja de zile pe care dorim s o acordm pentru plata cotizaiei. Interogarea selecteaz intrrile acelor membrilor care trebuie s-si achite cotizaia ntr-un interval mai mic dect numrul de zile specificat. Ca un caz special, o valoare a limitei egal cu O descoper membrii care trebuiau s-i achite cotizaia n trecut (adic membrii... care nu mai sunt membri, de fapt). '."- '-f' i., 338 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Dup ce am identificat nregistrrile care se preteaz la notificri, ce facem cu ele? O opiune ar fi expedierea unui mesaj direct din acelai script, dar este util s putem previzualiza lista nainte de a trimite mesajele. Din acest motiv, vom folosi o abordare n dou etape: Etapa 1: Rulai un script denumit need_renewal (impune rennoirea) pentru identificarea membrilor care trebuie s-i achite cotizaia. Putei pur i simplu examina aceast list sau o putei folosi ca date de intrare n a doua etap, pentru a expedia ntiinrile de plat a cotizaiei. Etapa 2: Rulai un script denumit renewal_notif y (notificare pentru rennoire), care trimite membrilor, prin email, o ntiinare de plat a cotizaiei. Scriptul trebuie s indice membrii fr adrese de e-mail, astfel nct s i putei contacta prin alte mijloace. Pentru prima parte a acestei sarcini, scriptul need_renewal() trebuie s-i identifice pe membrii care trebuie s-i achite cotizaia. Acest lucru se poate face astfel: # limita prestabilita este 30 de zile; reinitializeaza daca # este dat un argument numeric my ($limita) = 30; $limita = shift(eARGV) if @(ARGV) && ARGV[0] =- r\d+$/; warn "Utilizeaz o limita de $limita zile\n";

my ($sth) = $dbh->prepare (qq{ SELECT membru_id, email, nume, prenume, data_expirare, TO_DAYS (data_expirare) - TO_DAYS (CURRENT_DATE) AS zile FROM membru WHERE data_expirare < DATE_ADD(CURRENT_DATE, INTERVAL ? DAY) ORDER BY data_expirare, nume, prenume : $sth->execute ($limita); # transfera limita ca valoare # pentru cmpul de nlocuire while (my $hash_ref = $sth->fetchrow_hashref ()) { print join ("\t", $hash_ref ->{membru_id} , $hash_ref ->{email} , $hash_ref ->{nume}, $hash_ref ->{prenume}, $hash_ref ->{data_expirare} , $hash_ref ->{zile} . " zile" . "\n"; $sth->finish Capitolul 7 Interfaa API pentru Perl DBI 339 Datele de ieire ale scriptului need_renewal se prezint oarecum astfel (dumneavoastr vei obine rezultate diferite, deoarece rezultatele sunt determinate n raport cu data curent, care va fi diferit pentru dumneavoastr, cnd citii aceast carte, n raport cu data la care a fost scris cartea): 89 g.steve@pluto.com Garner Steve 1999-08-03 -32 zile 18 york_mark@earth.com York Mark 1999-08-24 -11 zile 82 john_edwards@venus.org Edwards John 1999-09-12 8 zile Observai c unii membri trebuie s-i achite cotizaia ntr-un numr negativ de zile, ceea ce nseamn c nu mai sunt membri! (Asta se ntmpl cnd folosii o eviden manual a nregistrrilor; oamenii scap printre degete. Acum, cnd avem informaiile ntr-o baz de date, descoperim c am omis cteva persoane nainte.) Cea de-a doua parte a sarcinii de ntiinare privind plata cotizaiilor implic utilizarea unui script renewal_notif y, care expediaz notificri prin e-mail. Pentru a facilita puin utilizarea acestui script, l putem determina s neleag" trei categorii de argumente din linia de comand: numere de identificare a membrilor, adrese de email si nume de fiiere. Argumentele numerice indic valorile identificatorilor de membru, iar argumentele care conin caracterul semnific adrese de e-mail. Orice alte argumente sunt interpretate ca fiind numele unui fiier care trebuie citit pentru a se gsi numerele de identificare sau adresele de e-mail. Aceast metod v permite s specificai membrii n funcie de numrul de identificare sau de adresa de e-mail, ceea ce putei face fie direct din linia de comand, fie prin menionarea lor ntr-un fiier, (n particular, putei folosi datele de ieire ale scriptului need_renewal ca date de intrare pentru scriptul renewal_notif y.) Pentru fiecare membru care urmeaz a primi o ntiinare, scriptul caut intrarea corespunztoare din tabelul membru, extrage adresa de e-mail si trimite un mesaj la adresa respectiv. Dac nu exist nici o adres de e-mail n intrarea respectiv, scriptul renewal_notif y genereaz un mesaj, pentru a v avertiza c aceti membri trebuie contactai ntr-un alt mod. Pentru a trimite mesaje de e-mail, scriptul renewal_notif y deschide un canal spre programul sendmail i determin trecerea mesajului de e-mail prin canalul respectiv.2 Calea de acces spre programul sendmail este configurat ca parametru aproape de nceputul scriptului. Poate c vei fi obligat s modificai aceast cale, deoarece amplasarea programului sendmail variaz de la un sistem la altul: # schimba calea conform configuraiei sistemului dumneavoastr my ($sendmail) = "/usr/lib/sendmail -t -oi"; Ciclul principal de prelucrare a argumentului funcioneaz dup cum urmeaz. Dac n linia de comand nu au fost specificate argumente, citim ca date de intrare datele de intrare standard (STDIN). n caz contrar, prelucrm fiecare argument transferndu-1 funciei interpret_argument (), pentru a fi clasificat ca numr de identificare, adres de e-mail sau nume de fiier: if (@ARGV = = O) # nu sunt argumente, citete STDIN # pentru a obine valorile Continuare 2 Acest procedeu nu este funcional pentru Windows, care nu dispune de programul sendmail. Va trebui s gsii un modul de expediere a mesajelor de e-mail i s-1 folosii pe acela. - N.A. 340 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare read_file (\*STOIN); else while (my 0arg = shift (3ARGV)) { # interpreteaz argumentul, cu recursivitatea numelui de fiier interpret_argument ($arg, 1); Funcia read_f ile( ) citete coninutul unui fiier (care se presupune c este deja deschis) i examineaz primul

cmp al fiecrei linii. (Dac alimentm scriptul renewaljiotif y cu datele de ieire ale scriptului need_renewal, fiecare linie are mai multe cmpuri, dar noi dorim s o examinm numai pe prima.) sub read_file { my ($fh) = script; my ($arg); while (defined ($arg = <$fh>)) { # elimina tot ce este plasat dincolo de coloana 1 , # inclusiv caracterul linie noua $arg =- s/\s.*//s; # interpreteaz argumentul, fr recursivitatea # numelui de fiier interpret_argument ($arg, 0); Funcia interpret_argument() clasific fiecare argument pentru a determina dac este un numr de identificare, o adres de e-mail sau un nume de fiier. Pentru numerele de identificare si adresele de e-mail, caut n intrarea de membru corespunztoare si o transfer scriptului notifyjnemberQ. Trebuie s fim ateni la membrii specificai n funcie de adresa de e-mail. Este posibil ca doi membri s aib aceeai adres (de exemplu soul i soia) i nu dorim s trimitem un mesaj cuiva pentru care mesajul respectiv nu este valabil. Pentru a evita aceast situaie, cutm identificatorul de membru care l corespunde unei adrese de e-mail, pentru a ne asigura c este unul singur. Dac adresa l corespunde mai multor numere de identificare, avem un echivoc i l vom ignora, dup afiarea unui mesaj de avertizare. Dac un argument nu apare sub forma unui numr de identificare sau al unei adrese de e-mail, se presupune c este numele unui fiier care trebuie citit pentru obinerea altor; Capitolul 7 Interfaa API pentru Perl DBI 341 date de intrare. i n acest caz trebuie s fim ateni - nu dorim s citim un fiier dac deja citim un alt fiier, pentru a evita posibilitatea unui ciclu infinit: sub interpret_argument { my ($arg, $recurse) = @_; my ($query, $ary_ref); if ($arg =- /"\d+$/) { notifyjnember ($arg); } elsif ($arg=~ //) # identificatorul numeric de membru # adresa de e-mail # obine identificatorul de membru asociat cu adresa # (trebuie sa fie unul singur) $query =qq{ SELECT membru_id FROM membru WHERE email = ? }; $ary_ref = $dbh->selectcol_arrayref ($query, undef, $arg); if (scalar (@{ary_ref>) == 0) { warn "Adresa de e-mail $arg nu corespunde nici unei intrri: se ignora\n"; } elsif (scalar (@{ary_ref>) > 1) { warn "Adresa de e-mail $arg corespunde mai multor intrri: se ignora\n"; else notifyjnember ($ary_ref->[0]); else # nume de fiier if (!$recurse) else warn "numele de fiier $arg se afla in interiorul unui fiier: se ignora\n"; open (IN, $arg) or die "Nu poate deschide $arg: $l\n"; Continuare 342 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare read_file (\*IN); close (IN); } Programul aferent funciei notify_member(), care trimite efectiv ntiinarea de plat a cotizaiei, este prezentat n continuare. Dac se dovedete c persoana membru nu are adres de e-mail, nu se poate face nimic, dar notif y_member() afieaz un avertisment, astfel nct s tii c trebuie s contactai persoana respectiv ntr-un alt

mod. Putei invoca showjnember cu numrul de identificare a persoanei membru prezentat n mesaj, pentru a vedea intrarea n totalitate - pentru a afla, de exemplu, care este numrul de telefon i adresa persoanei membru. # anuna membrul ca trebuie sa-si plteasc in curnd cotizaia sub notifyjnember { my ($membru_id) = shift; my ($query, $sth, Sentry_ref, col_name); warn "Anuna $membru_id...\n"; $query = qq{ SELECT * FROM membru WHERE membru_id = ? }; $sth = $dbh->prepare (Squery); $sth->execute ($membru_id); @col_name = {$sth->{NAME}}; $entry_ref = $sth->fetchrow_hashref (); $sth->finish (); if (!$entry_ref) # nu a fost gsit nici un membru! { warn " Nu a fost gsita NICI O INTRARE pentru membrul $member_id!\n"; return; } open (OUT, " | Ssendmail") or die "Nu poate deschide programul de posta\n"; print OUT EOF; To: $entry_ref->{email} Subject: Necesitatea plaii cotizaiei la Liga istorica Buna ziua. Va trebui ca in curnd sa va achitai cotizaia de membru al Ligii istorice. Speram ca vei gsi cteva minute pentru a contacta secretariatul Ligii n vederea achitrii cotizaiei. Coninutul intrrii l dumneavoastr din catalogul cu membri este prezentat mai jos. Va rugam | sa reinei mai ales data expirrii. Mulumim. Capitolul 7 Interfaa API pentru Perl DBI 343 EOF foreach my Scoljiame (@coljiame) printf OUT "%s: %s\n", Scoljiame, Sentryj^ef->{$coljiame}; close (OUT); Putei realiza chiar si mesaje mai atrgtoare; de exemplu, putei aduga o coloan la tabelul membru care s conin data trimiterii ultimei ntiinri. Astfel, nu vei trimite ntiinri prea frecvent, n forma n care este acum, vom presupune c nu vei rula programul mai frecvent dect o dat pe lun. Cele dou scripturi sunt acum terminate, deci le putei folosi astfel: % need_renewal > junk % (examinai fiierul junk pentru a vedea daca arata bine) % renewaljiotify junk Pentru a trimite notificri membrilor individuali, i putei specifica n funcie de numrul de identificare sau de adresa de e-mail: % need_renewal 18 g.steve@pluto.con Editarea intrrilor corespunztoare membrilor Ligii istorice Dup ce ncepem s trimitem ntiinri de plat a cotizaiei, putem presupune c unele din persoanele pe care leam anunat i vor achita cotizaia. Cnd se va ntmpla acest lucru, vom avea nevoie de o modalitate de a actualiza intrrile lor cu date noi de expirare, n capitolul urmtor, vom crea o modalitate de a edita nregistrrile membrilor dintr-un browser Web, dar aici vom crea un script n linie de comand editjnember, care v permite s actualizai intrrile folosind o metod simpl, de a solicita noile valori pentru fiecare component a intrrii. Scriptul funcioneaz astfel: Dac este invocat fr argumente n linia de comand, editjnember presupune c dorii s introducei un membru nou, solicit informaiile iniiale care vor fi amplasate n intrarea corespunztoare membrului si creeaz, o intrare nou. Dac este invocat cu un identificator numeric de membru n linia de comand, editjnember caut coninutul curent al intrrii, apoi solicit actualizri pentru fiecare coloan n parte. Dac introducei o valoare pentru o coloan, nlocuiete valoarea curent. Dac apsai pe tasta Enter, coninutul coloanei nu se modific. (Dac nu cunoatei numrul de identificare al unui membru, putei rula showjnember nume, pentru a afla care este acesta.) Probabil c editarea unei ntregi intrri n acest mod este o munc inutil, dac nu dorii dect s actualizai data plii cotizaiei unui membru. Pe de alt pane, un script ca acesta furnizeaz si o modalitate simpl, universal, de a actualiza orice component a unei intrri fr a cunoate limbajul SQL. (Un caz special este acela n care editjnember nu v va permite s modificai cmpul membru_id, deoarece acesta este atribuit n mod automat la

crearea unei intrri si nu trebuie modificat ulterior.) 344 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Prima informaie pe care editjnember trebuie s o cunoasc rezid n numele coloanelor din tabelul membru: # obine numele coloanelor din tabelul membru my ($sth) = $dbh->prepare (qq{ SELECT * FROM membru WHERE 1 = O }); $sth->execute (); my (@col_name) = @{$sth->{NAME}}; $sth->finish (); Apoi, putem introduce ciclul principal: if (@ARGV == 0) # daca nu exista argumente, # creeaz o intrare noua newjnember (\@col_name) ; else my (id); # in caz contrar, editeaz intrrile folosind # argumente precum identificatorii de membru # salveaz @ARGV, apoi goleste-1 pentru a citi din STDIN # nu folosete argumente precum numele de fiiere @id = 0ARGV; 0ARGV = (); # pentru fiecare valoare a identificatorului, # caut intrarea si apoi o editeaz while (my $id = shift (3id)) { my ($entry_ref ); $sth = $dbh->prepare (qq{ SELECT * FROM membru WHERE membru_id = ? $sth->execute $entry_ref = $sth->fetchrow_hashref (); $sth->finish (); if (!$entry_ref) { warn "Nici un membru nu are identificatorul = $id\n"; next; } edit_member (\@col_name, $entry_ref); Capitolul? Interfaa API pentru Perl DBI 345 Programul pentru crearea unei noi intrri de membru este prezentat n continuare. Programul solicit valori pentru fiecare coloan a tabelului membru, apoi emite o instruciune SELECT pentru a aduga o nregistrare nou: # creeaz o noua nregistrare de membru sub newjnember { my ($col_name_ref ) = shift; my ($entry_ref ) ; my ($col_val, $query, $delim); return unless prompt ("Creez o intrare noua? ") =- /"y/i; # cere valori noi; utilizatorul tasteaz valori noi, "nuli" pentru a # introduce o valoare NULL, "exit" pentru a parai programul prematur. foreach my ($col_name) (@{$col_name_ref }) { next if $col_name eq "membru_id"; # ignora cmpul cheie $col_val = col_prompt ($col_name, "", 0); next if $col_val eq " " ; # utilizatorul a apsat Enter return if $col_val =- /"exit$/i; # inchidere prematura $col_val = undef if $col_val =- /Anull$/i; $entry_ref->{$col_name} - $col_val; } # afieaz valorile, cere confirmarea nainte de inserie showjnember ($col_name_ref , $entry_ref); return unless prompt ("\nlnserez aceasta intrare? ") =- /"y/i; # construiete o interogare INSERT, apoi o emite. $query = "INSERT INTO membru'; $delim = " SET "; # pune "SET" inaintea primei coloane, "," # inaintea celorlalte foreach my $col_name (@{col_name_ref}) { # specifica valori numai pentru coloane care au primit o valoare next if Idefined ($entry_ref->{$col_name>) ; # funcia quote () citeaz undef drept cuvntul NULL, # (fr ghilimele), adic exact ceea ce dorim. $ query .= sprintf ("%s %s=%s", $delim, $col_name,

$dbh->quote ($entry_ref ->{$col_name})) ; $delim = " , " ; warn "Avertisment: intrarea nu a fost inserata?\n" unless $dbh->do ($query) ==1; 346 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Rutinele de solicitare utilizate de new_member( ) se prezint astfel: # pune o ntrebare, cere un rspuns sub prompt { my ($str) = shift; print STDERR $str; chomp ($str = <STDIN>); return ($str); # solicita valoarea unei coloane; afieaz valoarea curenta # in solicitare daca $show_current are valoarea "adevrat" sub col_prompt { my ($name, $val, $show_current) = @_; my ($prompt, $str); Sprompt = $name; Sprompt .= " [$val]" if $show_current; Sprompt .= ": "; print STDERR $prompt; chomp ($str = <STDIN>); return ($str ? $str : $val); } Motivul pentru care funcia col_prompt() preia un argument $show_current este acela c vom folosi aceast funcie i pentru solicitarea valorilor coloanelor din intrrile existente aferente membrilor atunci cnd scripul este utilizat pentru actualizarea unei intrri. $show_current va fi O cnd crem o intrare nou, deoarece nu exist valori curente de afiat, n acest din urm caz, promptul va afia valoarea curent, pe care utilizatorul o poate accepta, pur si simplu, prin apsarea tastei Enter. Programul pentru editarea intrrii aferente unui membru existent este similar cu aceea j pentru crearea unui membru nou. Totui, avem o intrare de lucru, deci rutina de soliei-: tare afieaz valorile intrrii curente, iar funcia edit_member() emite o instruciune\ UPDATE, nu o instruciune INSERT: # editeaz coninutul curent al unei intrri sub editjnember my ($col_name_ref , $entry_ref) my ($col_val, Squery, $delim); Capitolul 7 Interfaa API pentru Perl DBI 347 # afieaz valorile iniiale, cere permisiunea de a continua # si de a edita showjnember ($col_name_ref, $entry_ref); return unless prompt ("\nEditez aceasta intrare? ") =- /Ay/i; # solicita valori noi; utilizatorul tasteaz valoarea noua # pentru a inlocui valoarea existenta, "nuli" pentru a # introduce o valoare NULL, "exit" pentru a parai prematur # programul, respectiv Enter pentru a accepta valoarea existenta, foreach my $col_name (@{$col_name_ref}) next if $col_name eq "membru_id"; # ignora cmpul cheie $col_val = $entry_ref->{$col_name}; $col_val = "NULL" unless defined ($col_val); $col_val = col_prompt ($col_name, $col_val, 1); return if $col_val =/*exit$/i; # inchidere prematura $col_val = undef if $col_val =- /*null$/i; $entry_ref->{$col_name} = $col_val; # afieaz valorile noi, cere confirmare inainte de actualizare showjnember ($coljiamej~ef, $entryj~ef); return unless prompt ("\nActualizez aceasta intrare? ") =- /Ay/i; # construiete o interogare UPDATE, iar apoi o emite. $query = "UPDATE membru"; $delim = " SET "; # pune "SET" inaintea primei coloane, "," # inaintea celorlalte foreach my $coljiame (@{coljiamej*ef}) next if Scoljiame eq "membru_id"; # ignora cmpul cheie # funcia quote() citeaz undef drept cuvntul NULL # (fr ghilimele), adic exact ceea ce dorim. $ query .= sprintf ("%s %s=%s", $delim, Scoljiame, $dbh->quote ($entryj~ef->{$coljiame})); $delim = ","; Squery = " WHERE membru_id = ?"; warn "Avertisment: intrarea nu a fost actualizat?\n" unless $dbh->do ($query, undef, $entry_ref->{membru_id}) == 1; 348 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL O problem a funciei editjnember este aceea c nu execut validri ale valorilor de intrare. Pentru majoritatea cmpurilor din tabelul membru, nu prea avei ce valida - acestea nu sunt nimic altceva dect cmpuri ir. Dar

trebuie verificate valorile datelor de intrare pentru coloana data_expirare, pentru a exista sigurana c sunt date si nu altceva, ntr-o aplicaie de uz general pentru introducerea datelor, probabil c vei dori s extrageri informaii despre un tabel, pentru a determina tipurile tuturor coloanelor sale. Apoi, putei s formulai restricii de validare pe baza acestor tipuri, ceea ce este prea complicat pentru ceea ce vreau eu s prezint aici, deci voi aduga un artificiu rapid la funcia col_prompt () pentru a verifica formatul datelor de intrare, n cazul n care numele coloanei este "data_expirare". O verificare minimal a valorii datei se poate face astfel: sub col_prompt { my ($name, $val, $show_current) = _; my ($prompt, $str); if $show_current; loop: Sprompt = $name; $prompt .= " [$val] Sprompt .= ": "; print STDERR $prompt; chomp ($str = <STDIN>); # executa o verificare rudimentara a datei de expirare if ($str && $nume eq "data_expirare") # verifica formatul # datei de expirare $str =- r\d+r\d]\d+r\d]\d+$/ or goto loop; return ($str ? $str : $val); Modelul testeaz cele trei secvene de cifre separate prin caractere non-cifrice. Aceasta este numai o verificare parial, deoarece nu detecteaz valori precum "1999-14-92" ca fiind incorecte. Pentru a mbunti scriptul, putei insera verificri mai riguroase ale datelor i alte verificri, precum obligativitatea existenei unor valori nevide n cmpurile care conin numele i prenumele. Alte mbuntiri pot consta din omiterea actualizrii dac nu exist coloane modificate; respectiv de a anuna utilizatorul dac nregistrarea a mai fost modificat de altcineva n timp ce utilizatorul respectiv o edita. Putei face aceasta salvnd valorile originale ale coloanelor care compun intrrile membrilor si apoi scriind instruciunea UPDATE pentru a actualiza numai acele coloane care s-au modificat. Dac nu s-a modificat nici o coloan, instruciunea nici mcar nu mai trebuie emis. De asemenea, se poate scrie clauza WHERE astfel nct s includ AND nume_coloana = valoare_coloana pentru fiecare valoare original din coloan. Acest procedeu va cauza eecul instruciunii UPDATE dac

Capitolul 7 Interfaa API pentru Perl DBI 349 o alt persoan a modificat nregistrarea, ceea ce reprezint un indiciu al faptului c dou persoane ncearc s modifice intrarea simultan. Identificarea membrilor cu interese comune ai Ligii istorice Una din ndatoririle secretarului Ligii istorice este de a prelucra cererile provenite de la membrii care doresc o list cu ali membri care manifest un interes particular ntr-un anumit episod din istoria Statelor Unite, cum ar fi Marea Criz sau viaa lui Abraham Lincoln. Localizarea unor asemenea membri este destul de uoar cnd catalogul se afl sub forma unui document creat cu un procesor de texte, folosind funcia Find (gsire) a procesorului de texte. Totui, generarea unei liste care s fie compus numai din intrrile corespunztoare ale membrilor este mai dificil, deoarece implic o mulime de operaii de copiere i lipire. Cu MySQL, aceast operaie devine cu mult mai simpl, deoarece putem rula o interogare astfel: SELECT * FROM member WHERE interese LIKE "%lincoln%" ORDER BY nume, prenume Din pcate, rezultatele nu arat prea frumos dac rulm aceast interogare din clientul mysql. S alctuim un mic script DBI, denumit interese. Mai nti, scriptul verific pentru a se asigura c exist cel puin un argument denumit n linia de comand, deoarece n caz contrar nu dispune de criterii de cutare. Apoi, pentru fiecare argument, scriptul ruleaz o cutare avnd drept criteriu coloana interese a tabelului membru: @ARGV or die: "Utilizare: cuvntul cheie interese\n"; cautajnembri (shift (@ARGV)) while @ARGV; Pentru a cuta irul determinat de cuvntul cheie, inserm caractere de nlocuire % (procent) la fiecare capt al acestuia, astfel nct irul s poat fi gsit oriunde n coloana interese. Apoi, afim intrrile care satisfac criteriul de cutare: sub cautajnembri my ($interese) = shift; my ($sth, $numar);

prin "Caut rezultatele pentru cuvntul cheie: $interes\n\n\"; $sth = $dbh->prepare (qq{ SELECT * FROM membru WHERE interese LIKE ? ORDER BY nume, prenume }); # caut irul oriunde in cmpul "interese" $sth->execute ("%" . Sinterese . "%"); $numar = 0; while (my $hash_ref = $sth->fetchrow_hashref ()) { format_entry ($hash_ref ) ; ++$numar; } prin "Au fost gsite $numar intrari\n\n";

S-ar putea să vă placă și

  • Mysql 350
    Mysql 350
    Document28 pagini
    Mysql 350
    Lupu Adrian
    Încă nu există evaluări
  • Mysql 250
    Mysql 250
    Document29 pagini
    Mysql 250
    gabrielgabor22
    Încă nu există evaluări
  • Mysql 150
    Mysql 150
    Document30 pagini
    Mysql 150
    Lucian Musteata
    Încă nu există evaluări
  • Mysql 100
    Mysql 100
    Document32 pagini
    Mysql 100
    Виктор Которобай
    Încă nu există evaluări