Sunteți pe pagina 1din 29

250 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Versiunea modificat a funciei do_connect () este similar

cu versiunea anterioar^ toate punctele de vedere, cu dou excepii: Nu transfer un parametru db_name formei mai vechi a funciei mysql_real_c(j nect (), deoarece versiunea respectiv nu dispune de un atare parametru. Dac numele bazei de date nu este NULL, funcia do_connect () ape funcia mysql_select_db() pentru a transforma baza de date denumit baz de date curent. (Acest procedeu simuleaz efectul paramet db_name, care lipsete.) Dac baza de date nu poate fi selectat, do_connec afieaz un mesaj de eroare, nchide conexiunea si returneaz NULL pent indica eecul operaiei. Exemplul 2. Acest exemplu se bazeaz pe modificrile aduse funciei do_connect( j primul exemplu. Aceste modificri au ca rezultat trei seturi de apeluri la funciilfl eroare mysql_errno() i mysql_error() i este chiar obositor s scrii funciile res|| tive de fiecare dat cnd programul trebuie s protesteze" la apariia unei prob| De asemenea, programul de afiare a erorii este agresiv din punct de vedere vizt dificil de citit. Este mai uor s citii ceva de genul acesta: print_error(conn, "mysql_real_connect() failed"); Deci, haidei s ncapsulm scrierea erorilor ntr-o funcie print_error (). Putem : funcia astfel nct s efectueze o anumit operaie chiar si n situaia cnd conn este l Astfel, putem folosi print_error() dac apelul la funcia mysql_init() eueaz ! avem o combinaie de apeluri (unele pentru fprintf () i altele pentru print_erroC| Parc aud pe cineva din spate care obiecteaz: Pi nu suntei obligat s apelai; funcii de eroare de fiecare dat cnd trebuie s raportai o eroare, deci inteniei: facei programul dificil de citit, pentru ca exemplul dumneavoastr cu ncapsula arate mai bine. i de fapt nici mcar nu vei scrie tot programul de afiare a erorii scriei o singur dat i apoi folosii copierea i lipirea dac mai avei nevoie de Acestea sunt observaii corecte, la care voi rspunde astfel: Chiar dac folosii copierea i lipirea, aceste operaii sunt mai simplu i tuat cu seciuni mai scurte de program. Indiferent dac preferai sau nu s invocai ambele funcii de eroare la i raportare a unei erori, scrierea integral a programului de raportare a i n varianta lung" duce la tentaia de a folosi scurtturi i de a fi inconsa la raportarea erorilor. Plasarea codului de raportare a erorilor ntr-o container care este simplu de invocat atenueaz aceast tentat mbuntete consecvena programului. Dac v decidei vreodat s modificai formatul mesajelor dumneavc eroare, este mult mai uor dac trebuie s efectuai modificarea ntr-un! loc, dect n tot programul. Sau, dac v decidei s scriei mesajele de < ntr-un fiier jurnal n loc de (sau n afar de) a le scrie n stderr, este i piu dac trebuie s modificai numai funcia print_error(). Aceast flfl este mai puin expus la erori si, din nou, reduce tentaia de a face jumtate de treab i de a fi inconsecvent. *1 Capitolul 6 Interfaa API MySQL pentru C 251 Dac folosii un utilitar de depanare atunci cnd v testai programele, inseria unui punct de ntrerupere n funcia de raportare a erorilor este o modalitate convenabil de a determina programul s se ntrerup atunci cnd depanatorul detecteaz o condiie de eroare. Iat funcia noastr print_error( ) de afiare a erorilor: void print_error (MYSQL *conn, char "message) { fprintf (stderr, "%s\n", message); if (conn != NULL) { fprintf (stderr, Error %u (%s)\n", mysql_errno(conn) , mysql_error(conn)) ; Funcia print_error ( ) se afl n fiierul common . c, deci vom aduga un prototip al acesteia n fiierul common . h: void print_error(MYSQL *conn, char "message); Acum, funcia do_connect ( ) poate fi modificat pentru a folosi funcia print_er ror ( ) : MYSQL * do_connect(char *host_natne, char *user_name, char "password, char *db_name, unsigned int portjium, char *socket_name, unsigned int flags) { MYSQL *conn /* pointer spre variabila de tratare a conexiunii */ conn = mysql_init (NULL); /* aloca, iniializeaz variabila de tratare a conexiunii */ if (conn == NULL) { print_error(NULLj "mysql_init()f ailed (probably out of memory)"); return (NULL); } #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 32200 /* versiunea 3.22 si versiunile ulterioare */ if (mysql_real_connect (conn, host_name, user_name, password, db_name, port_num, socket_name, flags) == NULL) { print_error(conn, "mysql_real_connect() failed"); return (NULL);

#else /* pentru MySQL anterior versiunii 3.22 */ Continuare 252 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare if (mysql_real_connect (conn, nost_name, user_name, password, port_num, socket_name, flags) == NULL) print_error(conn, "mysql_real_connect() failed"); return(NULL); if (db_name != NULL) /* simularea efectului parametrului db_name */J if (mysql_select_db (conn, db_name) != 0) print_error(conn, "mysql_select_db() failed"); mysql_close(conn); return(NULL); #endif return (conn); /* conexiunea a fost stabilita */ Fiierul nostru surs principal, clients.c, este asemntor cu client2.c, dar auj| eliminate toate liniile de program de conexiune i ntrerupere a conexiunii, f^ nlocuite cu apeluri la funciile container. Deci, fiierul surs se prezint astfel: /* clienta.c */ tfinclude <stdio.h> #include <mysql.h> tfinclude "common.h" tfdefine def_host_name NULL tfdefine def_user_name NULL #define def_password NULL ^define def_db_name NULL MYSQL *conn; int main (int argc, char *argv[]) /* gazda la care se va stabili conexi nea(valoare prestabilita = localhos /* nume utilizator (valoare prestabil = numele dumneavoastr de deschide a sesiunii de lucru) */ /* parola (valoare prestabilita = nici una) */ /* baza de date de utilizat (valoare J prestabilita = nici una) */ /* pointer spre variabila de tratare conexiunii */ Capitolul 6 Interfaa API MySQL pentru C 253 conn = do_connect(def_host_name, def_user_name, def_password, def_db_name, def_port_num, def_socket_name, 0); if (conn == NULL) exit(1); /* aici are loc activitatea aplicaiei */ do_disconnect(conn); exit(O); Client 4 - Obinerea parametrilor de conexiune la rulare Acum, cnd dispunem de un program de conexiune uor de modificat si blindat" n cazul apariiei erorilor, suntem pregtii s aflm cum putem face lucruri mai inteligente dect s folosim parametri de conexiune NULL, ca de exemplu s permitem utilizatorului s specifice aceste valori la rulare. Clientul anterior, clients, continu s prezinte un dezavantaj semnificativ, n sensul c parametrii de conexiune sunt codai n program. Pentru a modifica oricare dintre aceste valori, trebuie s editai fiierul surs i s-1 recompilai, ceea ce nu este foarte convenabil, mai ales dac dorii s punei programul la dispoziia altor persoane. O modalitate frecvent folosit de specificare a parametrilor de conexiune la rulare este de a folosi opiunile din linia de comand. Programele din distribuia sistemului MySQL accept parametrii de conexiune ntr-una din cele dou forme specificate n tabelul 6.1. Tabelul 6.1 Opiuni standard din linia de comand pentru MySQL Parametru Numele gazdei Numele de utilizator Parola Numrul portului Numele soclului Form scurt -h nume_gazda -u nume_utilizator -p sau -pparola_dv -P numar_port -S nume soclu Form lung - - host=mj/ne_gazc/a - -user=nume_i/tilizator --password sau

--password=parola_dv - -port=/iu/nar_port --socket=nume soclu L Pentru consecvena cu programele client MySQL standard, clientul nostru va accepta aceleai formate, ceea ce este simplu de realizat, deoarece biblioteca client include o 'Vineie pentru analiza opiunilor. h plus, clientul nostru va putea s extrag informaiile din fiierele cu opiuni. Aceasta y permite s plasai parametrii de conexiune n fiierul - /. my. cnf (adic fiierul . my. cnf din catalogul dumneavoastr de baz), astfel nct s nu fie necesar specificarea lor n de comand. Biblioteca client faciliteaz cutarea fiierelor cu opiuni MySQL i extragerea din acestea a tuturor valorilor relevante. Prin adugarea a numai cteva linii 254 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL n programul dumneavoastr, putei determina programul s recunoasc fiiere!^ J opiuni si nu trebuie s reinventai roata prin scrierea propriilor dumneavoastr p| grame pentru aceasta. Sintaxa fiierelor cu opiuni este descris n Anexa E, Refer* de programe MySQL". Accesul la coninutul fiierelor cu opiuni Pentru a citi fiierele cu opiuni n cutarea valorilor parametrilor de conexiune, folc funcia load-def aults(). Aceast funcie caut fiierele cu opiuni, le analizeaz cont tul pentru a descoperi orice grupuri de opiuni care v intereseaz si rescrie vector argumente al programului dumneavoastr (tabloul argv[ ]) pentru a insera infor din aceste grupuri sub form de opiuni ale liniei de comand la nceputul table argv [ ]. Astfel, opiunile apar ca i cum ar fi fost specificate n linia de comand. Ca; cnd analizai opiunile comenzii, obinei parametrii de conexiune ca parte a cif normal de analiz a opiunilor. Opiunile sunt adugate la nceputul tabloului argv( nu la sfrit, astfel nct, dac parametrii de conexiune sunt ntr-adevr specificai rn de comand, acetia s apar mai trziu (si implicit s redefineasc) orice opiuni adt de funcia load_def aults (). J" Iat un mic program, show_argv, care prezint modul de utilizare a funciei load_def aur i care ilustreaz modificarea vectorului cu argumente prin acest procedeu: /* show_argv.c */ ^include <stdio.h> ^include <mysql.h> char *groupsl] = { "client", NULL }; int main(int argc, char *argv[]) { int i; my_init(); printf("Vector cu argumente original:\n"); for(i = 0; i < argc; i++) printf("arg %d: %s\n", i, argvi]); load_defaults("my", groups, &argc, &argv); printf("Vector cu argumente modificat:\n"); for(i = 0; i < argc; i++) prirvtf ("arg %d: %s\n", i, argv[i]); exit(O); Capitolul 6 Interfaa API MySQL pentru C 255 Programul de prelucrare a fiierului cu opiuni implic urmtoarele: groups [] este un tablou ir de caractere care indic grupurile din fiierele cu opiuni care v intereseaz. Pentru programele client, specificai ntotdeauna cel puin meniunea "client" (pentru grupul [client]). Ultimul element al tabloului trebuie s fie NULL. my_init () este o rutin de iniializare care execut unele operaii de pornire impuse de funcia load_defaults(). Funcia load_def aults () preia patru argumente: prefixul fiierelor dumneavoastr cu opiuni (acesta trebuie s fie ntotdeauna "my"), tabloul care menioneaz grupurile de opiuni care v intereseaz, respectiv adresa numrului de argumente i a vectorului de argumente ale programului dumneavoastr. Nu transferai valorile numrului de argumente i ale vectorului; transmitei n schimb adresele lor, deoarece funcia load_def aults () trebuie s le modifice valorile. Reinei, mai ales, c dei argv este un pointer, trebuie s transferai &argv, adresa pointerului respectiv. Funcia show_argv i afieaz argumentele de dou ori: prima dat aa cum le-ai specificat n linia de comand, apoi n urma modificrilor efectuate de load_defaults(). Pentru a vedea efectele funciei load_def aults (), asigurai-v c avei un fiier .my. cnf n catalogul dumneavoastr de baz, cu unii parametri specificai pentru grupul [client]. S presupunem c fiierul .my .cnf se prezint astfel: [client] user=paul password=secret host=o_gazda n aceast situaie, prin executarea programului show_argv se obine urmtorul rezultat: % show_argv a b Vector cu argumente original:

arg 0: show_argv arg 1; a arg 2: b Vector cu argumente modificat: arg 0: show_argv arg 1; --user=paul arg 2: --password=secret arg 3; --host=o_gazda arg 4: a arg 5: b Este posibil ca ntre datele de ieire ale programului show_argv s vedei unele opiuni care nu se gseau nici n linia de comand, nici n fiierul dumneavoastr -/ .my .cnf. n acest caz, opiunile respective au fost probabil specificate ntr-un fiier cu opiuni la nivel de sistem. De fapt, funcia load_def aults () caut fiierele /etc /my .cnf si my .cnf din catalogul de date MySQL nainte de a citi fiierul .my.cnf din catalogul dumnea-I voastr de baz. (n Windows, funcia load_defaults() caut fiierele C:\my.cnf, l c: \mysql\data\my. cnf, respectiv fiierul my. ini din catalogul \Windows\System). 256 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Programele client care folosesc funcia load_def aults () specific aproape ntc "client" n lista cu grupuri d opiuni (pentru a putea obine tpate valorile client^ rale din fiierele cu opiuni), dar putei cere i valori care sunt specifice propriulvuj neavoastr program. Pur i simplu nlocuii instruciunea char *groups[] = { "client", NULL }; cu urmtoarea linie de program: , ;|| char *groups.[J = { "show^arov", "client", NULL }; Apoi, putei aduga un grup [show_argv] la fiierul dumneavoastr -/ .my.cnf: sj. [client] user=paul ? password=secret host=o_gazda [show_argvj hostalta_gazda ;: n urma acestor modificri, o nou invocare a programului show_argv va ave rezultat, dup cum urmeaz: % show_argv a b ' Vector cu argumente original: arg 0: show_argv arg 1: a arg 2: b Vector cu argumente modificat: arg 0: show_argv arg 1: --user=paul arg 2: --password=secret arg 3: --host=o_gazda arg 4: --host=alta_gazda arg 5: a arg 6: b Ordinea n care apar valorile opiunilor n tabloul cu argumente este deterr ordinea n care acestea sunt menionate n fiierul dumneavoastr cu opk ordinea n care grupurile cu opiuni sunt menionate n tabloul groups [J. , nseamn c probabil vei dori s specificai grupuri specifice programelor [client] din fiierul dumneavoastr cu opiuni. Astfel, dac specificai 6 o| ambele grupuri, valoarea specific programului va avea prioritate. Putei vedea; n exemplul prezentat anterior: opiunea host a fost specificata att rl grupul'^ ct si n grupul [ show_argv ] dar, deoarece grupul [ show_argv] apare ultimul n \ opiuni, valoarea sa host apare mai trziu n vectorul cu argumente i are pric Funcia load_def aults () nu selecteaz valori din parametrii dumneavoastr^ Dac doni s folosii valorile unor variabile de mediu precum MYSQL_TCP MYSQL_UNIX_PORT, trebuie s v ocupai personal de acest lucru prin intermediul l getenv(). Nu voi aduga aceast funcionalitate la clienii notri, dar iat un i verificare a valorilor a dou dintre variabilele de mediu standard legate de M; Capitolul 6 Interfaa API MySQL pentru C 257 extern char *getenv(); char *p; u: int port_nura; char *oqKet_name; if ((p, = OetenvCMYSQL^TCP^PQRT") != NUU), portjium, ?. atoi (p); r if ((P QetewMYSQLJJNIXJ'OR'!) 1= NU14-) socketjiame = p; , , ;. -.;

n cazul clienilor MySQL standard, valorile variabilelor de mediu au o prioritate mai redus dect valorile specificate n fiierele cu opiuni sau n linia de comand. Dac verificai variabilele membru i doriri s respectai convenia respectiv, verificai variabilele de mediu nainte, nu dup apelarea funciei load_def aults () sau prelucrarea opiunilor din linia de comand. - 't Analiza argumentelor din linia de comand n acest moment, putem prelua toi parametrii de conexiune n vectorul cu argumente, dar avem nevoie de o modalitate de analiz a vectorului. Funcia getoptO*are exact aceast destinaie. Funcia getopt_long() este ncorporat n biblioteca client MySQL, deci putei avea acces la aceasta ori de cte ori stabilii legturi cu funcii din biblioteca respectiv, n fiierul dumneavoastr surs, trebuie si includei fiierul antet getopt. h. Putei copia acest fiier antet din catalogul include al distribuiei surs MySQL n catalogul n care v dezvoltai programul client. !< ' "r -' ;; i . . 't. C -.-": ' - Funcia load_def aults () i sewritoteo Poate v punei ntrebri cu privire la implicaiile legate de .spionarea" proceselor pe care le poate avea solicitarea ca funcia load_def aults () s insereze textul parolelor n lista dumneavoastr cu argumente, deoarece programe precum ps pot afia listele cu argumente pentru procese arbitrare. Nu este nici o problem, deoarece ps afieaz coninutul original al tabloului argv[ ]. Toate argumentele de tip parol create de funcia load_def aults () indic spre o regjune pe care funcia respectiv o aloc pentru sine. Acea regiune nu face parte din vectorul original, deci programul ps nu o vede niciodat. Pe de alt parte, o parol care este specificat n linia de comand apare n ps dac dumneavoastr nu v ngrijii s o tergei. Seciunea .Analiza argumentelor din linia de comand" v arat cum s procedai. Programul urmtor, show_param, folosete funcia load_defaults() pentru citirea fiierelor cu opiuni, apoi apeleaz-funcia getopt_long<) pentru analiza vectorului cu argumente. show_param arat ce se ntmpl n fiecare faza a prelucrrii argumentelor, Prin efectuarea urmtoarelor aciuni: ! Configureaz valorile prestabilite pentru numele gazdei, numele de utilizator i parola. 2- Afieaz valorile originale ale parametrilor de conexiune i valorile din vectorul cu argumente. ' ; u'' 258 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL 3. Apeleaz funcia load_defaults() pentru a rescrie vectorul cu argumente astfdij acesta s reflecte coninutul fiierului cu opiuni, dup care afieaz vectorul re 4. Apeleaz funcia getopt_long () pentru prelucrarea vectorului cu argumente, ap seaz valorile rezultante ale parametrilor i ceea ce a mai rmas n vectorul cu : show_param v permite s exersai diferite modaliti de specificare a parametrik conexiune (situai n fiierele cu opiuni sau n linia de comand) i s vedei rezult prin afiarea valorilor care vor fi folosite pentru stabilirea unei conexiuni. show_ este util pentru a v face o idee privind ceea ce se va ntmpla n urmtorul nostru^ gram client, atunci cnd corelm acest program de prelucrare a parametrilor cu noastr de conectare do_connect (). Iat cum se prezint fiierul show_param.c: /* show_param.c */ ^include <stdio.h> tfinclude <stdlib.h> /* necesar pentru atoi() */ ^include "getopt.h" char *groups[] = { "client", NULL }; struct option long_options[] = {"host", {"user", {"password", {"port", {"socket", { 0, 0, 0, 0 } required_argument, required_argument, optional_argument, required_argument, NULL, NULL, NULL, NULL, 'u'}, 'P'}, 'P'}, required_argument, NULL, 'S'}, int main (int argc, char *argv[]) char *host_name = NULL; char *user_name = NULL; char *password = NULL; unsigned int portjium = 0; char *socket_name = NULL; int i; int c, option_index; my_init(); printf ("Parametrii originali ai conexiunii:\n")

Capitolul 6 Interfaa API MySQL pentru C 259 printf ("host name: %s\n" , host_name ? hostjiame : "(nuli)"}; printf ("user name: %s\n", user_name ? user_name : "(nuli)"); printf ("password: %s\n", password ? password : "(null)"); printf ("port number: %u\n", port_num); printf ("socket name: %s\n", socket_name ? socket_name : "(null)"); printf ("Vector cu argumente original: \n" ); for (i = 0; i < argc; i++) printf ("arg %d: %s\n", i, argv[i]); load_defaults("my", groups, &argc, &argv); printf ("Vector cu argumente modificat dup load_defaults() :\n"); for (i = 0; i < argc; i++) printf (" arg %d: %s\n" , i, argv[i]); while ((c = getopt_long(argc, argv, "h:p: :u:P:S" , long_options, &option_index)) != EOF) { switch (c) { case ' h ' : host_name = optarg; break; case 'u': user_name = optarg; break; case ' p ' : password = optarg; break; case 'P' : portjnum = (unsigned int) atoi (optarg); break; case 'S' : socket_name = optarg; break; argc -= optind; /* avanseaz dincolo de argumentele */ argv += optind; /* care au fost prelucrate de getopt_long() */ printf ("Parametrii conexiunii dup getopt_long() :\n") printf ("host name: %s\n", host_name ? hostjiame : "(nuli)"); Continuare 260 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare printf ("user name: %s\n", user_name ? user_name : "(nuli)"); printf ("password: %s\n", password ? password : "(null)"); printf ("port number: %u\n", port_num); printf ("socket name: %s\n", socket_name ? socket_name : "(null)"); printf ("Vector cu argumente dup getopt_long():\n"); for (i = 0; i < argc; i++) printf("arg %d: %s\n", i, argv[i]); exit(O); } Pentru a prelucra vectorul cu argumente, show_argv folosete funcia getopt_long (), care o apelai, n mod caracteristic, ntr-un ciclu: 3 while ((c = getopt_long(argc, argv, "h: p: :u:P:S", long_options, &option_index)) != EOF) { /* opiune de proces */ } Primele dou argumente ale funciei getopt_long() le constituie numrul de argur al programului dumneavoastr i vectorul cu argumente. Al treilea argument l re zint literele de opiuni care dorii s fie recunoscute. Acestea sunt formele cu mj scurt ale opiunilor programului dumneavoastr. Literele de opiune pot fi urmat dou puncte, de dou puncte dublate (::) sau de nici un asemenea caracter, pent arta c opiunea trebuie s fie urmat, poate fi urmat, respectiv nu este urmat valoare a opiunii. Cel de-al patrulea argument, long_options, este un pointer spr tablou cu structuri de opiune, fiecare din aceste structuri specificnd informaii ] o opiune pe care dorii ca programul dumneavoastr s o neleag. Rolul su este st| Iar cu acela al irului de opiuni din al treilea argument. Cele patru elemente ale fiec structuri long_opt ions [] sunt urmtoarele: Numele lung al opiunii. O valoare a opiunii. Valoarea poate fi required_argument (argument obligate optional_argument (argument facultativ) sau no_argument (fr argument), indic dac opiunea trebuie urmat, poate fi urmat, respectiv nu este urmat valoare a opiunii. (Aceste valori au acelai rol ca si caracterul dou puncte, car dou puncte dublate si respectiv absena oricrui caracter din al treilea argument, < conine irul de opiuni.) Un argument indicator (flag). Putei folosi acest argument pentru a stoca un j la o variabil. Dac se gsete opiunea, getopt_long () stocheaz n variabil valet specificat de al patrulea

argument. Dac indicatorul este NULL, getopt_long () coK reaz variabila optarg astfel nct s indice spre orice valoare care urmeaz dup of i returneaz numele scurt al opiunii, n cazul nostru, tabloul long_options[ ] sg fic NULL pentru toate opiunile. Astfel, getopt_long () returneaz fiecare argumefl cum l ntlnete astfel nct s poat fi prelucrat n instruciunea switch. Capitolul 6 Interfaa API MySQL pentru C 261 Numele scurt (dintr-un singur caracter) al opiunii. Numele scurte precizate n tabloul long_options[ ] trebuie s corespund literelor folosite n irul cu opiuni pe care l transferai drept al treilea argument al funciei getopt_long(), n caz contrar programul dumneavoastr fiind incapabil de a prelucra n mod adecvat argumentele din linia de comand. Tabloul long_options[] trebuie terminat cu o structur ale crei elemente sunt toate egale cu 0. Cel de-al cincilea argument al funciei getopt_long() este un pointer la o variabil de tip int. getopt_long () stocheaz n aceast variabil indexul structurii long_options [ ] care corespunde ultimei opiuni ntlnite. (show_param nu face nimic cu aceast valoare.) Reinei c opiunea pentru parol (specificat sub forma - -password sau -p) poate lua o valoare opional. Cu alte cuvinte, o putei specifica sub forma password sau --pass-word=parola_dumneavoastr dac folosii forma de opiune lung, respectiv -p sau - -pparola_dumneavoastra dac folosii forma de opiune scurt. Natura opional a valorii parolei este indicat! de caracterul:: plasat dup litera p din irul cu opiuni, precum i de specificaia optional_argument din tabloul long_options[]. De regul, clienii MySQL v permit s omitei valoarea parolei n linia de comand, dup care v solicit aceast valoare. Acest procedeu v permite s evitai furnizarea parolei n linia de comand, ceea ce mpiedic pe alii s v citeasc parola prin spionarea" procesului. Cnd vom scrie urmtorul client, client4, vom aduga la acesta logica de verificare a parolei. Iat un exemplu de invocare a programului show_param si a datelor de ieire rezultate (presupunnd c -/.my.cnf are acelai coninut ca n exemplul cu programul show_argv): % show_paran -h inca_o_gazda x Parametri de conexiune originali: host name: (null) user name: (null) password: (null) port number: 0 socket name: (null) Vector cu argumente original: arg 0: show_parara arg 1: -h arg 2: inca_o_gazda arg 3: x Vector cu argumente modificat dup load_defaults<): arg 0: show_param arg 1: --user=paul arg 2: --password=secret arg 3: --host=o_gazda arg 4: -h arg 5: inca_o_gazda Continuare 262 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare arg 6: x Parametri de conexiune dup getopt_long(): host name: inca_o_gazda user name: paul password: secret port number : O socket name: (null) Vector cu argumente dup getopt_long(): arg 0: x Datele de ieire arat c numele gazdei este selectat din linia de comand (redc valoarea din fiierul cu opiuni), precum si c numele de utilizator si parola provin ld fiierul cu opiuni. getopt_long() analizeaz corect opiunile, indiferent dac ac sunt specificate n forma de opiune scurt (-h nume_gazda) sau n forma de op lung (-user=paul, --password=secret). Acum, s eliminm liniile de program care nu au dect rolul de a ilustra modii funcionare a rutinelor de tratare a

opiunilor i s folosim restul programului cai pentru un client care se conecteaz la un server n conformitate cu opiunile cari i furnizate ntr-un fiier cu opiuni sau n linia de comand. Fiierul surs re client4.c, se prezint astfel: /* client4.c */ tfinclude <stdio.h> #include <stdlib.h> /* pentru atoi() */ #include <mysql.h> #include "common.h" ^include "getopt.h" #define def_host_name NULL tfdefine def_user_name NULL tfdefine def_password NULL #define def db name NULL /* gazda la care se va stabili conexiunea(valoare prestabilita = .< localhost) */ /* nume utilizator (valoare prestabil numele dumneavoastr de deschidere^ sesiunii de lucru) */ /* parola (valoare prestabilita = nicij una) */ /* baza de date de utilizat (valoare " prestabilita = nici una) */ char *groups[] = { "client", NULL }; struct option long_options[] = {"host", required_argument, NULL, 'h'}, {"user", {"password", {"port", {"socket", { O, O, O, O } Capitolul 6 Interfaa API MySQL pentru C 263 required_argument, NULL, 'u'}, optional_argument , NULL, 'p'}, required_argument , NULL, 'P'}, required_argument, NULL, 'S'}, MYSQL *conn; /* pointer spre variabila de tratare a conexiunii */ int main (int argc, char *argv{]) char *host_name = def_host_name; char *user_name = def_user_name; char *password = def_password; unsigned int portjium = def_port_num; char *socket_name = def_socket_name; char *db_name = def_db_natne; char passbuf[100]; int ask_password = 0; int i; int c, option_index=0; my_init(); load_defaults("my", groups, &argc, &argv); while ((c = getopt_long(argc, argv, "h:p::u:P:S", long_options, &option_index)) != EOF) switch (c) case 'h1: host_name = optarg; break; case 'u': user_name = optarg; break; case 'p': if (!optarg) /* nu este data nici o valoare */ ask_password = 1; else /* copiaz parola, terge originalul */ Continuare 264 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare i (void) strncpy (passbuf, optarg, sizeof (passbuf )-1); passbuf [sizeof (passbuf ) -1 ] = '\0'; password = passbuf; while (*optarg)

*optarg++ = ' ' ; } break; case 'P1: port_num = (unsigned int) atoi (optarg); break; case 'S1: socket_name = optarg; break; argc -= optind; /* avanseaz dincolo de argumentele */ argv += optind; /* prelucrate de getopt_long ( ) */ if (argc > 0) { db_name = argv[0]; --argc; ++argv;; if (ask_password) password = get_tty_password (NULL); conn = do_connect (host_name, user_name, password, db_name, port_num, socket_name, 0 if (conn == NULL) exit(1); /* aici se insereaz codul aplicaiei propriu -zise */ do_disconnect(conn) ; exit(O); } n comparaie cu programele clieni, client2 i clients pe care le-am creat ane client4 efectueaz cteva operaii pe care nu le-am ntlnit pn acum: Permite specificarea numelui bazei de date n linia de comand, dup opiunii* i sunt analizate de getopt_long ( ) . Aceast comportare este similar cu aceea a clie standard din distribuia MySQL.

Capitolul 6 Interfaa API MySQL pentru C 265 terge valoarea parolei din vectorul cu argumente, dup copierea acesteia. Procedeul are ca scop reducerea ferestrei de timp pe durata creia o parol specificat n linia de comand este vizibil pentru ps sau pentru alte programe de determinare a strii sistemului. (Fereastra este minimizat, nu eliminat. Specificarea parolelor n linia de comand continu s reprezinte un pericol pentru securitate.) Dac o opiune de parol a fost dat fr o valoare, clientul solicit utilizatorului o parol folosind funcia get_tty_password(). Aceasta este o rutin de tip utilitar din biblioteca client, care solicit o parol fr a o reflecta pe ecran. (Biblioteca client este plin de asemenea bunti. O lectur a sursei programelor client MySQL este instructiv, deoarece putei afla informaii despre aceste rutine i despre modul de utilizare a lor.) Putei ntreba: De ce nu apelai pur i simplu funcia getpassO?" Rspunsul este c nu toate sistemele dispun de aceast funcie; Windows este un asemenea sistem. Funcia get_tty_password() este portabil pe mai multe sisteme, deoarece este configurat s se ajusteze la idiosincraziile sistemului. Programul client4 reacioneaz n conformitate cu opiunile pe care le specificai. Pentru a complica situaia, s presupunem c nu exist nici un fiier cu opiuni. Dac invocai programul client4 fr argumente, acesta se conecteaz la localhost i transfer serverului numele dumneavoastr UNIX de deschidere a sesiunii de lucru, fr parol. Dac n schimb invocai client4 aa cum s-a artat mai sus, programul va cere o parol (nu exist nici o valoare a parolei imediat dup -p), se conecteaz la o_gazda i transfer serverului numele de utilizator un_utilizator si parola pe care o tastai: % client4 -h o_gazda -u un_utilizator -p o_baza_de_date De asemenea, client4 mai transfer funciei do_connect() numele bazei de date o_baza_de_date, pentru a o transforma n baz de date curent. Dac exist un fiier cu opiuni, coninutul acestuia este prelucrat i folosit pentru modificarea n consecin a parametrilor de conexiune. Anterior, am ncapsulat programele ntr-o veselie", crend funcii container pentru conectarea la, respectiv deconectarea de la server. Se poate pune ntrebarea dac este necesar inseria unei logici de analiz a opiunilor si ntr-o funcie container. Este posibil, cred, dar nu voi proceda astfel. Logica de analizare a opiunilor nu este la fel de consecvent n diferite programe ca i programul de conexiune: programele accept deseori i alte opiuni, n afara opiunilor standard pe care tocmai le-am analizat, iar programe diferite au tendina de a accepta alte seturi diferite de opiuni suplimentare. Astfel, este dificil de scris o funcie care s standardizeze ciclul de prelucrare a opiunilor. De asemenea, spre deosebire de stabilirea conexiunii, pe care un program poate dori s o efectueze de mai multe ori pe durata execuiei sale (i care este, implicit, un bun candidat pentru ncapsulare), analiza opiunilor se execut de obicei o singur dat: la nceputul programului. Prin ceea e am fcut pan acum, am realizat o operaie necesar pentru fiecare client MySQL: conectarea la server folosind parametri adecvai. Trebuie s tii s v conectai, desigur. Dar acum tii cum s procedai, iar detaliile acestui proces sunt implementate n scheletul programului client (cliervt4. c), deci nu mai trebuie s v gndii la ele. Aceasta inseamn c v putei concentra asupra a ceea ce v intereseaz cu adevrat - capacitatea de a obine acces la coninutul bazelor dumneavoastr dedate. Toat aciunea" aplicaiei 266 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL dumneavoastr se va produce n intervalul dintre apelurile la funciile do_connect (.-) do_disconnect(), dar ceea ce avem acum servete drept cadru general, pe care l pi folosi pentru numeroi clieni diferii. Pentru a scrie un program nou, procedai astfebl

1. Facei o copie a programului clientA.c. 2. Modificai ciclul de prelucrare a opiunilor, dac acceptai opiuni suplimentare, alfai dect opiunile standard pe care le cunoate programul clientA.c. 3. Adugai propriul dumneavoastr program de aplicaie specific, plasndu-1 hj apelurile de conectare i de deconectare. i ai terminat. Ideea de a respecta o disciplin a construciei unui schelet al programului client era i propune ceva uor de folosit pentru a stabili, respectiv pentru a elimina o conexiune, i fel nct dumneavoastr s v putei concentra cu adevrat asupra a ceea ce de facei. Acum suntei liberi de a proceda astfel, ca o ilustrare a principiului c discig duce la libertate. Prelucrarea interogrilor Acum, cnd tim cum s ncepem i s ncheiem o conversaie cu serverul, este mor s aflm cum se poate dirija conversapa n timpul desfurrii acesteia. Aceast secii arat cum se poate discuta" cu serverul pentru prelucrarea interogrilor. Fiecare interogare pe care o rulai implic urmtoarele etape: 1. Construii interogarea. Modalitatea de construcie a interogrii depinde de conii tul interogrii - mai exact, de faptul dac aceasta conine sau nu date binare. 2. Emitei interogarea trimind-o Ia server n vederea executrii. 3. Prelucrai rezultatul interogrii. Aceasta depinde de tipul interogrii emise.'*' exemplu, o instruciune SELECT returneaz rnduri de date pe care urmeaz s le] lucrai. O instruciune INSERT nu'procedeaz astfel. ;3 Un factor care trebuie avut n vedere la construirea interogrilor l constituie care se va utiliza pentru trimiterea acestora la server. Rutina cu un caracter mai gen de emitere a interogrilor este mysql_real_query(). Cu aceast rutin, furnizai uit garea sub forma unui ir numrat (un ir plus o lungime). Trebuie s pstrai lungimii interogrilor dumneavoastr i s o trimitei funciei mysql_real_query{jyi turi de irul n sine. Deoarece interogarea este un ir numrat, coninutul su pe oricare, inclusiv date binare sau octei NULL. Interogarea nu este tratat ca un ir .c termin cu o valoare zero1. ., Cealalt funcie de emitere a interogrilor, i anume mysql_query(), este mai resfif n ceea ce privete coninutul irului interogrii, dar deseori este mai simplu de utu Interogrile pe care le transmitei funciei mysql_query () trebuie s fie iruri ter cu zero, ceea ce nseamn c nu pot conine octei zero n textul interogrii. (Pre 1 Prin valoare zero" nu trebuie s se neleag cifra O, ci valoarea echivalent cu zero pentru i date al irului respectiv. - N.T. Capitolul 6 Interfaa API MySQL pentru C 267 octeilor zero n cadrul interogrii determin interpretarea eronat a acesteia ca fiind mai scurt dect n realitate.) n general vorbind, dac interogarea dumneavoastr poate conine date binare arbitrare, ar putea conine octei zero, deci nu trebuie s folosii mysql_query(). Pe de alt parte, cnd lucrai cu iruri care se termin n zero, v putei permite luxul de a construi interogri folosind funciile ir din biblioteca C standard pe care probabil c le cunoatei deja, precum strcpy() si sprintf (). Un alt factor de care trebuie inut cont n construirea interogrilor este dac trebuie s efectuai operaii de anulare a semnificaiilor unor caractere (escaping). Aceste operaii sunt necesare dac dorii s construii interogri folosind valori care conin date binare sau alte caractere problematice, cum sunt ghilimelele i caracterele backslash. Despre acest aspect vom discuta n seciunea Codificarea datelor problematice n cadrul interogrilor". O schem simpl de tratare a interogrilor se prezint astfel: if (mysql_query (conn, query) != 0) /* eec; raporteaz eroarea */ else /* succes; afla care a fost efectul interogrii */ Att mysql_query () ct i mysql_real_query() returneaz zero pentru interogrile care reuesc, respectiv valori diferite de zero n caz de eec. A spune c o interogare a reuit" nseamn c serverul a acceptat-o ca fiind valabil i c a fost capabil s o execute. Nu se specific nimic despre efectul interogrii. De exemplu, nu se arat c o interogare SELECT a selectat vreun rnd sau c o instruciune DELETE a ters vreun rnd. Determinarea efectului real al interogrii implic prelucrri suplimentare. O interogare poate eua dintr-o diversitate de motive. Unele cauze comune includ urmtoarele: Interogarea conine o eroare de sintax. Interogarea este incorect din punct de vedere semantic - de exemplu, o interogare care se refer la o coloan inexistent a unui tabel. Nu avei suficiente privilegii pentru a avea acces la datele la care se face referire n interogare. Interogrile pot fi grupate n dou mari categorii: interogrile care nu returneaz un rezultat i cele care returneaz un rezultat. Interogrile pentru instruciuni precum INSERT, DELETE i UPDATE se ncadreaz

toate n categoria celor care nu returneaz nici un rezultat. Interogrile respective nu returneaz nici un rnd, nici mcar n cazul interogrilor care modific baza dumneavoastr de date. Singura informaie pe care o Primii const n numrul rndurilor afectate. Interogrile pentru instruciuni precum SELECT i SHOW se ncadreaz n categoria celor care returneaz un rezultat; la urma urmelor, scopul emiterii acestor instruciuni este de a primi u" rezultat. Setul de rnduri produs de o interogare care returneaz date se numete set de 268 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL rezultate. Acesta este reprezentat n MySQL de tipul de date MYSQL_RES, o struc conine valorile de date pentru rnduri i, de asemenea, metadate referitoare la valoril| ar fi numele coloanelor i lungimile valorilor de date). Un set de rezultate vid (adie care conine zero rnduri) este diferit de noiunea fr rezultat". Tratarea interogrilor care nu returneaz un set de rezultate Pentru a prelucra o interogare care nu returneaz un set de rezultate, emitei int rea folosind mysql_query() sau mysql_real_query (). Dac interogarea reuete, pj afla care a fost numrul rndurilor inserate, terse sau actualizate apelnd mysql_affected_rows(). Exemplul urmtor prezint modul de tratare a unei interogri care nu returneaz i set de rezultate: if (mysql_query (conn, "INSERT INTO tabel SET nume = 'Numele meu") print_error ("Instruciunea INSERT a euat"); else printf ("Instruciunea INSERT a reuit: %lu rnduri afectate\n',^ (unsigned long) mysql_affected_rows (conn)); Observai cum rezultatul funciei mysql_affected_rows () este convertit la-'j unsigned long n vederea afirii. Aceast funcie returneaz o valoare de( my_ulonglong, dar ncercarea de afiare direct pe ecran a unei valori de acest ra reuete pe unele sisteme. (De exemplu, am observat c reuete sub FreeBSI eueaz sub Solaris.) Conversia valorii la tipul unsigned long i utilizarea unui l de afiare ca %lu rezolva problema. Aceleai consideraii se aplic si la orice alte! care returneaz valori de tipul my_ulonglong, precum mysql_num_rows| mysql_insert_id(). Dac dorii ca programele dumneavoastr client s fie por mai multe sisteme, reinei acest lucru. Funcia mysql_affected_rows() returneaz numrul rndurilor afectate de dar semnificaia noiunii de rnduri afectate" depinde de tipul interogrii, instruciunile INSERT, REPLACE sau DELETE, semnific numrul rndurilor nlocuite, respectiv terse. Pentru UPDATE, este vorba de numrul rndurilor : ceea ce nseamn numrul rndurilor pe care MySQL le-a modificat efectiv. M) actualizeaz un rnd n cazul n care coninutul su este acelai cu acela al actualizare. Aceasta nseamn c, dei un rnd poate fi selectat pentru actualizare (<i clauza WHERE a instruciunii UPDATE), este posibil ca rndul respectiv s nu fie; De fapt, semnificaia noiunii de rnduri afectate" pentru instruciunea UPDATE estjl cum controversat, deoarece unii vor ca aceasta s nsemne rnduri corespu adic numrul rndurilor selectate pentru actualizare, chiar dac operaia de nu modific valorile pe care le conin acestea. Dac aplicaia dumneavoastr ne atare semnificaie, putei obine aceast comportare prin solicitare direct n mc Capitolul 6 Interfaa API MySQL pentru C 269 conectrii la server. Transferai funciei mysql_real_connect() o valoare flags egal cu CLIENT_FOUND_ROWS. Aceeai valoare poate fi transmis ca argument flags i funciei do_connect (); aceasta va trimite valoarea mai departe funciei mysql_real_connect (). Tratarea interogrilor care returneaz un set de rezultate Interogrile care returneaz date le grupeaz pe acestea ntr-un set de rezultate, pe care l manipulai dup emiterea interogrii prin apelarea uneia din funciile mysql_query () sau mysql_real_query(). Este important de reinut c, n MySQL, SELECT nu este singura instruciune care returneaz rnduri. La fel procedeaz i SHOW, DESCRIBE si EXPLAIN. Pentru toate aceste instruciuni, dup emiterea interogrii trebuie s efectuai prelucrri suplimentare legate de tratarea rndurilor. Tratarea unui set de rezultate implic urmtoarele etape: Generarea setului de rezultate prin apelarea funciei mysql_store_result () sau a funciei mysql_use_result(). Aceste funcii returneaz un pointer MYSQL_RES n caz de reuit, respectiv NULL n caz de eec. Mai trziu, vom discuta despre diferenele dintre funciile mysql_store_result () si mysql_use_result (), precum i condiiile n care se alege utilizarea uneia n detrimentul alteia. Pentru moment, exemplele noastre folosesc mysql_store_result(), care returneaz imediat rezultatele de la server si le stocheaz la programul client. Apelarea funciei mysql_fetch_row() pentru fiecare rnd al setului de rezultate. Aceast funcie returneaz o valoare MYSQL_ROW, care este un pointer spre un tablou de iruri care reprezint valorile din fiecare coloan a rndului. Modul n care utilizai rndul depinde de aplicaia dumneavoastr. Putei s afiai valorile din coloane, putei efectua calcule statistice cu aceste valori sau orice altceva. Funcia mysql_f etch_row() returneaz NULL atunci cnd n setul de rezultate nu au mai rmas rnduri. Cnd ai terminat lucrul cu setul de rezultate, apelai funcia mysql_f ree_result() pentru anularea alocrii memoriei pe care o folosete. Dac omitei aceast operaie, aplicaia dumneavoastr va provoca scurgeri" de memorie, (n cazul aplicaiilor cu durata mare de rulare, este important mai ales s v debarasai n mod adecvat

de seturile de rezultate; n caz contrar, vei descoperi cum controlul sistemului dumneavoastr este ncet-ncet preluat de procese care consum cantiti tot mai mari din resursele sistemului.) Exemplul urmtor prezint modul de prelucrare a unei interogri care returneaz un set de rezultate: MYSQL_RES *res_set; if (mysql_query(conn, "SHOW TABLES FROM mysql") 1=0) print_erTor(conn, "mysql_query() ratat"); else res_set = tnysql_store_result (conn); /* genereaz set de rezultate */ if (res_set == NULL) print_error(conn, "mysql_store_result() ratat"); else Continuare 270 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare { l* prelucreaz setul de rezultate, apoi elibereaz memoria alocata acestuia */ process_result_set (conn, res_set); mysql_free_result(res_set); Aici am triat puin, apelnd o funcie process_result_set () pentru manipularea i rnd. nc nu am definit funcia respectiv, deci trebuie s facem aceast operaie, n g ral, funciile de tratare a setului de rezultate se bazeaz pe un ciclu care arat astfel: MYSQL_ROW rnd; while ((rand = mysql_fetch_row (res_set)) != NULL) /* se folosete intr-un fel coninutul rndurilor */ Valoarea MYSQL_ROW returnat de funcia mysql_f etch_row() este un pointer spr tablou de valori, deci accesul la fiecare valoare se reduce la accesul rndului rowl i] 't i variaz, ntre O si numrul de coloane ale rndului, minus o unitate. Trebuie reinute cteva caracteristici importante ale tipului de date MYSQL_R01N: MYSQL_ROW este un tip pointer, deci variabilele de acest tip se declar sub MYSQL_ROW rnd, nu sub forma MYSQL_ROW *rand. irurile dintr-un tablou MYSQL_ROW se termin n zero. Totui, dac o coloan j conine date binare, poate conine octei zero, deci nu trebuie s tratai valoare un ir care se termin n zero. Determinai lungimea coloanei, pentru a ti ct dfi este valoarea din coloan. Valorile tuturor tipurilor de date, chiar i ale tipurilor numerice, sunt returna form de iruri. Dac dorii s tratai o valoare sub form de numr, trebuie vertii personal irul. Valorile NULL sunt reprezentate prin pointeri NULL n cadrul tabloului MYSQL_HG excepia situaiei cnd ai declarat o coloan ca fiind NOT NULL, trebuie s ve ntotdeauna dac valorile din coloana respectiv sunt sau nu pointeri NULL. Aplicaiile dumneavoastr pot utiliza n orice mod coninutul fiecrui rnd. Din i ilustrative, s afim rndurile, cu valorile coloanelor separate prin tabulatori. aceasta, avem nevoie de o funcie suplimentar, mysql_num_fields(), din client; aceast funcie ne indic numrul de valori (coloane) pe care le conine i Iat liniile de program pentru funcia process_result_set(): void process_result_set (MYSQL *conn, MYSQL_RES *res_set) MYSQL_ROW rnd ; Capitolul 6 Interfaa API MySQL pentru C unsigned int i; while ((rand = mysql_fetch_row (res_set)) != NULL) for (i = 0; i < mysql_num_fields (res_set); 271 if (i > 0) fpute ('\t', stdout); printf ("%s", row[i] != NULL ? row[i] : "NULL"); } fputc ('\n', stdout); } if (mysql_errno (conn) 1= 0) print_error (conn, "mysql_fetch_row() ratat"); else printf ("%lu rnduri returnate\nn, (unsigned long) mysql_num_rows (res_set)); Funcia process_result_set () afieaz fiecare rnd ntr-un format cu date separate prin tabulatori (i afind valorile NULL sub forma cuvntului "NULL"), urmat de numrul rndurilor regsite. Numrul de rnduri respectiv este disponibil prin apelarea funciei mysql_num_rows (). Ca i funcia mysql_af f ected_rows (), mysql_num_rows () returneaz o valoare de tip my_ulonglong, deci convertii acea valoare la tipul unsigned long si folosii pentru afiarea valorii formatul ' %lu'. Ciclul de preluare a rndurilor este urmat de un test pentru detectarea erorilor. In cazul n care creai setul de rezultate cu mysql_store_result (), o valoare NULL returnat de funcia mysql_f etch_row() are ntotdeauna semnificaia nu mai exist rnduri". Totui, n cazul n care creai setul de rezultate cu mysql_use_result (), o

valoare NULL returnat de funcia mysql_fetch_row() poate avea semnificaia nu mai exist rnduri", dar poate indica i apariia unei erori. Testul nu face altceva dect s permit funciei process_rezult_set() s detecteze erorile, indiferent de modul n care v creai setul de rezultate. Aceast versiune a funciei process_result_set() execut ntr-un mod oarecum minimal afiarea valorilor coloanelor, o abordare care i are dezavantajele sale. De exemplu, s presupunem c executai aceast interogare: SELECT nume, prenume, ora, stat FROM preedinte Vei primi urmtoarele date de ieire: Adams John Braintree MA Adams John Quincy Braintree MA Arthur Chester A. Fairfield VT Buchanan James Mercersburg PA Bush George W. Milton MA Carter James E. Jr. Plains GA Cleveland Grover Caldwell Nj 272 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Am fi putut crea un aspect estetic pentru datele de ieire prin furnizarea de inforr precum etichetele de coloane i prin alinierea pe vertical a valorilor. Pentru ace avem nevoie de etichete i trebuie s cunoatem cea mai lung valoare din fie coloan. Informaiile respective sunt disponibile, dar nu ca parte a valorilor datelor coloane; acestea fac parte din metadatele setului de rezultate (date despre date). Dup vom generaliza puin rutina de tratare a interogrilor, n seciunea Utilizarea datelor setului de rezultate" vom scrie un program de formatare estetic a rezultatei Afiarea datelor binare Valorile din coloane care conin date binare ce pot include octei zero nu vor fi afiate n mod i ztor dac se folosete specificatorul de format' %s' al funciei printf (); aceast funcie ateap ir care se termin n zero i va afia valoarea coloanei numai pn la primul octet zero. Pentru < binare, cel mai bine este s folosii lungimea coloanei, astfel nct s putei afia valoarea completai! putea folosi, de exemplu, funciile f write () sau pute (). O rutin de uz general pentru tratarea interogrilor Exemplele anterioare de tratare a interogrilor au fost scrise tiind dac instrucia trebuie s returneze sau nu date. Acest lucru a fost posibil deoarece interogrile' codate hard n program; am folosit o instruciune INSERT, care nu returneaz un i rezultate, respectiv o instruciune SHOW TABLES, care returneaz un set de rezultatelil Totui, nu cunoatei ntotdeauna care este tipul de instruciune pe care l repr interogarea. De exemplu, dac executai o interogare pe care o citii de la tastat dintr-un fiier, aceasta poate fi orice instruciune arbitrar. Nu avei cum s tii n dac s v ateptai sau nu ca instruciunea respectiv s returneze rnduri. i Evident c nu dorii s analizai interogarea pentru a determina ce tip de instruct; reprezint. Oricum, nu este o operaie la fel de simpl pe ct pare. Nu este suficid! examinai primul cuvnt, deoarece interogarea poate ncepe cu un comentariu, cum urmeaz: /* comentariu */ SELECT ... . Din fericire, nu trebuie s cunoatei n prealabil tipul de interogare pentru a o '^j trata n mod adecvat. Interfaa API n C pentru MySQL permite scrierea unei rut uz general pentru tratarea interogrilor, care prelucreaz corect orice tip de inst indiferent dac aceasta returneaz sau nu un set de interogri. nainte de a scrie liniile de program pentru rutina de tratare a interogrilor, s modul de funcionare a acesteia: Emite interogarea. Dac eueaz, am terminat. Dac interogarea reuete, apeleaz funcia mysql_store_result() pentru a rndurile de la server i pentru a crea un set de rezultate. Dac funcia mysql_store_result() eueaz, atunci fie interogarea nu returne set de rezultate, fie s-a produs o eroare n timpul ncercrii de regsire a setului, j face diferena ntre aceste rezultate transfernd variabila de tratare a conexiunij funcia mysql_f ield_count() i verificnd valoarea acesteia, dup cum urmeaz5:! Capitolul 6 Interfaa API MySQL pentru C 273 Dac mysql_f ield_count ( ) este diferit de zero, indic o eroare: interogarea ar fi trebuit s returneze un set de rezultate, dar nu a fcut-o. Acest lucru se poate ntmpla din diverse motive. De exemplu, poate c setul de rezultate a fost prea mare pentru spaiul de memorie alocat sau poate c s-a produs o cdere pe segmentul de reea dintre client i server n timpul prelurii rndurilor. O uoar complicaie a acestei proceduri este aceea c mysql_f ield_count( ) nu exista anterior versiunii MySQL 3.22.24. n versiunile anterioare, se folosete n schimb funcia mysql_num_f ields ( ) . Pentru a scrie programe care funcioneaz n orice versiune a sistemului MySQL, includei urmtorul fragment de program n orice fiier care apeleaz funcia mysql_f ield_count ( ) : #if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID<32224 tfdefine mysql_field_count mysql_num_fields #endif Acest program impune tratarea tuturor apelurilor la funcia mysql_f ield_count ( ) ca apeluri la funcia mysql_num_f ields ( ) pentru versiuni MySQL anterioare versiunii 3.22.24.

Dac funcia mysql_field_count() returneaz O, nseamn c interogarea nu a returnat nici un set de rezultate. (Acest fapt arat c interogarea a fost o instruciune de tip INSERT, DELETE sau UPDATE.) Dac apelul la funcia mysql_store_result ( ) reuete, interogarea a returnat un set de rezultate. Prelucrai rndurile apelnd la funcia mysql_fetch_row() pn cnd returneaz NULL. Listingul urmtor prezint o funcie care prelucreaz orice interogare, date fiind o variabil de tratare a conexiunii i un ir de interogare terminat n zero: #if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID<32224 #define mysql_field_count mysql_num_fields #endif void process_query (MYSQL *conn, char *query) { MYSQL_RES *res_set; unsigned int field_count; if (mysql_query (conn, query) != 0) /* interogare ratata */ { print_error (conn, "process_query() ratat"); return; Continuare 274 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare l* interogarea a reuit; determina daca returneaza date sau nu */ ^ res_set = mysql_store_result (conn); if (res_set == NULL) /* nu a fost returnat nici un de rezultate */ / * lipsa unui set de rezultate inseamna ca s-a produs o eroare * sau ca nu a fost returnat nici un set de rezultate? */ if (mysql_field_count (conn) > 0) /* * se atepta un set de rezultate, dar mysql_store_resultQ * a returnat nimic; aceasta inseamna ca s-a produs o eroaf */ print_error (conn, 'Problema la prelucrarea setului de rezultat else } /* * nu a fost returnat nici un set de rezultate; interogat * nu a returnat date (nu a fost de tip SELECT, SHOW, * DESCRIBE sau EXPLAIN) , deci raporteaz numrul de * rnduri afectate de interogare */ printf ("%lu rnduri afectate\n", (unsigned long) mysql_affected_rows (conn)); } else /* a fost returnat un set de rezultate */ { /! /* prelucreaz rndurile, apoi elibereaz memoria alocata setu| de rezultate */ process_result_set (conn, res_set); mysql_free_result (res_set); } Capitolul 6 Interfaa API MySQL pentru C 275 Metode alternative pentru prelucrarea interogrilor Versiunea funciei process_query ( ) prezentat anterior are trei proprieti: Folosete funcia mysql_query ( ) pentru a emite interogarea. Folosete funcia mysql_store_query ( ) pentru a regsi setul de rezultate. Dac nu se obine nici un set de rezultate, folosete mysql_field_count() pentru a face diferena ntre apariia unei erori i un set de rezultate neateptat. Sunt posibile abordri alternative pentru toate aceste trei aspecte ale tratrii interogrilor: Putei folosi un ir de interogare numerotat i funcia mysql_real_query() n locul unui ir de interogare care se termina n zero i al funciei mysql_query ( ) . Putei crea setul de rezultate apelnd funcia mysql_use_result() n locul funciei mysql_store_result ( ) . Putei apela funcia mysql_error() n locul funciei mysql_field_count(), pentru a afla dac regsirea setului de rezultate a euat sau dac nu exist nici un set de regsit.

Oricare din aceste metode (sau chiar toate) se pot folosi n cadrul funciei process_query(). Iat o funcie process_real_query() care este analog cu process_query ( ), dar care folosete toate cele trei alternative: void process_real_query (MYSQL "conn, char *query, unsigned int len) { MYSQL_RES *res_set; unsigned int field_count; if (mysql_real_query (conn, query, len) != 0) /* interogarea a euat */ { print_error (conn, "process_real_query() ratata"); return; /* interogarea a reuit; determina daca returneaza date sau nu */ res_set = mysql_use_result (conn) ; if (res_set == NULL) /* nu a fost returnat nici un set de rezultate */ /* * lipsa unui set de rezultate nseamn ca s-a produs o eroare * sau ca nu a fost returnat nici un set de rezultate? */ if (mysql_errno (conn) 1= 0) /* eroare */ Continuare s, 276 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare '* print_error (conn, "Problema la prelucrarea setului de rezultate" else nu a fost returnat nici un set de rezultate; interogarea * nu a returnat date (nu a fost de tip SELECT, SHOW, * DESCRIBE sau EXPLAIN), deci raporteaz numrul de * rnduri afectate de interogare */ printf ("%lu rnduri afectate\n", (unsigned long) mysql_affected_rows (conn)); t else /* a fost returnat un set de rezultate */ { /* prelucreaz rndurile, apoi elibereaz memoria alocata setuluiJ de rezultate */ process_result_set (conn, res_set); mysql_free_result (res_set); O comparaie ntre funciile mysql_store_result() si mysql_use_result() Funciile mysql_store_result() i mysql_use_result () sunt asemntoare, n sensul-j ambele preiau un argument de tip variabil de tratare a conexiunii i returneaz un set] rezultate. Totui, diferenele dintre cele dou funcii sunt foarte mari. Principala difere rezid n modalitatea n care sunt regsite de la server rndurile din setul de rezult Cnd o apelai, mysql_store_result() regsete imediat toate rndurile. Funqj mysql_use_result() iniiaz operaia de regsire, dar nu obine practic nici un rnd.1 schimb, presupune c vei apela funcia mysql_fetch_row() ulterior pentru re nregistrrilor. Aceste abordri diferite n ceea ce privete maniera de regsire a rnd"* dau natere la toate celelalte diferene dintre cele dou funcii. Seciunea de fa cor funciile, pentru a v permite s facei alegerea cea mai adecvat pentru o aplicaie Cnd mysql_store_result () regsete un set de rezultate de la server, preia rndurile,: memorie pentru ele i le stocheaz la client. Apelurile ulterioare la mysql_fetch_row(), vor returna niciodat o eroare, deoarece pur i simplu extrag un rnd din structura de < care conine deja setul de rezultate. Cnd funcia mysql_fetch_row() returneaz nseamn ntotdeauna c ai ajuns la sfritul setului de rezultate. Prin contrast, mysql_use_result() nu regsete rnduri, n schimb, iniiaz o oj de regsire rnd cu rnd, pe care trebuie s o finalizai personal prin apelarea func L Capitolul 6 Interfaa API MySQL pentru C 277 mysql_fetch_row() pentru fiecare rnd. n acest caz, dei o valoare NULL returnat de mysql_fetch_row() n mod normal continu s nsemne c s-a ajuns la sfritul setului de rezultate, este de asemenea posibil ca n timpul comunicaiei cu serverul s se fi produs o eroare. Putei face deosebirea ntre cele dou rezultate prin apelarea funciei mysql_errno () sau a funciei mysql_error (). mysql_store_result() prezint cerine de memorie i de prelucrare mai ridicate dect mysql_use_result{), deoarece ntregul set de rezultate este pstrat la client. Surplusul de memorie alocat i de complexitate a structurii de date este mai ridicat si un client care regsete seturi de rezultate mari ntmpin riscul de a epuiza memoria. Dac intenionai s regsii mai multe rnduri simultan, poate dorii s folosii n schimb funcia

mysql_use_result (). Funcia mysql_use_result () are cerine de memorie mai reduse, deoarece este necesar alocarea unui spaiu de memorie pentru manipularea numai a unui singur rnd la un moment dat. Aceasta poate duce la o cretere a vitezei, deoarece nu configurai o structur de date att de complex pentru setul de rezultate. Pe de alt parte, tnysql_use_result() solicit mai mult serverul, care trebuie s memoreze rnduri din setul de rezultate pn cnd clientul crede de cuviin c trebuie s le regseasc pe toate. Astfel, funcia mysql_use_result() nu este o alegere bun pentru anumite tipuri de clieni: Clieni interactivi, care se deplaseaz de la un rnd la altul, la cererea utilizatorului. (Nu dorii ca serverul s fie forat s atepte trimiterea rndului urmtor numai pentru c utilizatorul s-a decis s ia o pauz de cafea.) Clienii care execut un volum mare de prelucrri ntre operaiile de regsire a dou rnduri. n ambele tipuri de situaii, clientul nu reuete s regseasc rapid toate rndurile din setul de rezultate. Astfel, serverul devine ocupat si faptul poate avea un impact negativ asupra altor clieni, deoarece tabelele din care regsii date sunt blocate la citire pe durata interogrii. Toi clienii care ncearc s actualizeze aceste tabele sau s insereze rnduri n interiorul acestora sunt blocai. Compensarea cerinelor de memorie suplimentare determinate de funcia my sql_store_result () reprezint anumite avantaje ale accesului la ntregul set de rezultate simultan. Toate rndurile din set sunt disponibile, deci avei acces aleator la ele: funciile mysql_data_seek(), mysql_row_seek() i mysql_row_tell() v permit s obinei accesul la rnduri n orice ordine dorii. Cu funcia mysql_use_result (), putei obine accesul la rnduri numai n ordinea n care acestea sunt regsite de mysql_fetch_row(). Dac intenionai s prelucrai rnduri ntro alt ordine dect cea secvenial returnat de server, trebuie s folosii n schimb funcia mysql_store_result (). De exemplu, dac avei o aplicaie care permite utilizatorului s se deplaseze nainte i napoi printre rndurile selectate de o interogare, cea mai bun soluie o reprezint aplicaia mysql_store_result(). Cu funcia mysql_store_result() putei obine anumite tipuri de informaii despre coloane care nu sunt disponibile atunci cnd folosii mysql_use_result(). Numrul de rnduri al setului de rezultate este obinut prin apelarea funciei mysql_num_rows(). Dimensiunile maxime ale valorilor din fiecare coloan sunt stocate n membrul 278 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL max_width al structurii de informaii privind coloanele MYSQL_FIELD. mysql_use_result(), mysql_num_rows() nu returneaz valoarea corect dect at cnd ai preluat toate rndurile, iar valoarea max_width este inaccesibil, deoarece pe fi calculat numai dup ce au fost examinate datele din fiecare rnd. Deoarece mysql_use_result () execut mai puine operaii dect mysql_store_resurb| impune o cerin inexistent n mysql_store_result(): clientul trebuie s apele funcia mysql_f etch_row() pentru fiecare rnd din setul de rezultate, n caz contrar, l nregistrrile rmase n set devin parte a setului de rezultate al urmtoarei interogri i j produce o eroare de desincronizare. Acest lucru nu se ntmpl n cazul func mysql_store_result() deoarece, atunci cnd funcia respectiv returneaz, toate rai durile au fost deja preluate. De fapt, dac folosii mysql_store_result(), nu mai treb| s apelai personal funcia mysql_fetch_row(). Acest fapt poate fi util pentru intere n care v intereseaz numai s obinei un rezultat nevid, nu i coninutul rezultat De exemplu, pentru a afla dac un tabel my_tbl exist, putei executa aceast intere SHOW TABLES LIKE "my_tbl" Dac, dup ce apelai funcia mysql_store_result (), valoarea funciei mysql_num_ra este diferit de zero, tabelul exist. Nu este necesar apelarea funciei mysql_f etch_rqi (Evident, trebuie s apelai funcia mysql_f ree_result.) Dac dorii s furnizai un maximum de flexibilitate, oferii utilizatorilor opiunea-a selecta oricare din cele dou metode de prelucrare a setului de rezultate, mys mysqldump sunt dou programe care execut aceast operaie. Acestea folosesc fur mysql_store_result() n mod prestabilit, dar comut la mysql_use_result() specificai opiunea --quick. Utilizarea metadatelor privind setul de rezultate Seturile de rezultate nu conin numai valorile coloanelor aferente rndurilor de dat si informaii despre date. Aceste informaii poart numele de metadate ale setul* rezultate i includ: Numrul de rnduri i de coloane din fiecare set de rezultate, disponibile prin ap funciilor mysql_num_rows(), respectiv mysql_num_f ields(). Lungimea valorii fiecrei coloane dintr-un rnd, disponibil prin apelarea mysql_fetch_lengths(). Informaii despre fiecare coloan, cum ar fi numele i tipul coloanei, limea i valorilor din fiecare coloan, precum si tabelul din care provine coloana. Aceste ia maii sunt stocate n structurile MYSQL_FIELD, care sunt obinute n mod ca prin apelarea funciei mysql_f etch_f ield (). Anexa F descrie structura MYSQL_Fi detaliu si prezint toate funciile care furnizeaz accesul la informaiile din colc Disponibilitatea metadatelor depinde n parte de metoda de prelucrare a se rezultate. Aa cum s-a artat n seciunea anterioar, dac dorii s folosii va numrului de rnduri sau ale lungimii maxime a coloanei, trebuie s creai setul de;t ae folosind mysql_store_result(), nu mysql_use_result(). Metadatele setului de rezultate sunt urile pentru luarea unor decizii privind prehidi datelor din setul de rezultate: Capitolul 6 Interfaa API MySQL pentru C 279

Informaiile privind numele si limea coloanei sunt utile pentru producerea unor rezultate frumos formatate, cu titluri de coloan i linii aranjate pe vertical. Numrul de coloane se folosete pentru a determina numrul de iteraii al unui ciclu care prelucreaz valorile succesive ale coloanelor pentru rndurile de date. Putei folosi numerele de rnduri sau de coloane dac trebuie s alocai structuri de date care depind de cunoaterea numrului de rnduri sau de coloane din setul de rezultate. Putei determina tipul de date al unei coloane. Aceast informaie v permite s v dai seama dac o coloan reprezint un numr, dac poate conine date binare si altele. Anterior, n seciunea Tratarea interogrilor care returneaz date", am scris o versiune a funciei process_result_set() care afia coloane provenite din rnduri ale setului de rezultate folosind un format de separare a datelor prin tabulator!. Acest procedeu este adecvat pentru anumite scopuri (cnd dorii s importai datele ntr-o foaie de calcul tabelar), dar nu este un format de afiare estetic pentru examinare vizual sau pentru tiprire. S ne reamintim c versiunea anterioar a funciei process_result_set() furniza un rezultat ca acesta: Adams John Braintree MA Adams John Quincy Braintree MA Arthur Chester A. Fair-field VT Buchanan James Mercersburg PA Bush George W. Milton MA Carter James E. Jr Plains GA Cleveland Grover Caldwell NJ S efectum unele modificri ale funciei process_result_set() astfel nct s genereze date de ieire n form tabelar prin atribuirea unui titlu pentru fiecare coloan i ncasetarea" acesteia. Versiunea revizuit va afia aceleai rezultate, ntr-un format mai simplu de examinat: nume prenume ioras stat Adams Adams Arthur Buchanan Bush Carter Cleveland John John Quincy Chester A. James George W. James E. , Jr. Grover i Braintree ' Braintree ; Fair-field i Mercersburg ; Milton Plains i Caldwell MA MA VT PA MA GA NJ

O 1. 2. L schi general a algoritmului de afiare este urmtoarea: Se determin limea de afiare pentru fiecare coloan. Se afieaz un rnd de etichete de coloan ncasetate" (delimitate prin bare verticale i precedate, respectiv urmate de rnduri formate din liniue). Se afieaz valorile din fiecare rnd al setului de rezultate, cu fiecare coloan ncase-tat (delimitat prin bare verticale) i aliniat pe vertical, n plus, numerele sunt afiate aliniate la dreapta i se afieaz cuvntul "NULL" n locul valorilor NULL. 280 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL 4. La sfrit, se afieaz un numr al rndurilor regsite. Acest exerciiu reprezint o bun demonstraie a utilizrii metadatelor setului de n ae. Pentru a afia rezultatele aa cum s-a artat mai sus, trebuie s tim mai lucruri despre setul de rezultate, nu numai valorile datelor incluse n rnduri. Poate v gndii: Hmm, descrierea aia arat suspect de asemntor cu modul n mysql i afieaz rezultatele". Da, aa este, iar dumneavoastr suntei invitat s parai codul surs al programului mysql cu programul final al fum process_result_set() revizuite. Cele dou nu sunt identice, dar comparaia dou abordri ale aceleiai probleme este instructiv. Mai nti, trebuie s determinm limea de afiare a fiecrei coloane. Listingul v arat cum s procedai. Calculele se bazeaz n ntregime pe metadatele setuli rezultate i nu se face nici o referire la valorile din rnduri: MYSQL_FIELD *field; unsigned int i, col, len; /* determina limile de afiare ale coloanelor mysql_field_seek (res_set, 0); for (i = 0; i < mysql_num_fields (res_set); ;j rj |

vj Ai '3i j* 4 ' field = mysql_fetch_field (res_set); col_len = strlen (field->narae); if (col_len < field->max_length) col_len = field->max_length; if (col_len < 4 && IIS NOT NULL (field->flags)) col_len = 4; /* 4 este lungimea cuvntului "NULL" */ field->max_length = col_len; /* reinitializarea informaiilor despre coloana */ Limile coloanelor sunt calculate prin parcurgerea ciclic a structurilor MYSQL_FI! pentru coloanele din setul de rezultate. Ne poziionm pe prima structur prin apela funciei mysql_fetch_seek(). Apelurile ulterioare la funcia mysql_fetch_fiel| returneaz pointer! spre structurile coloanelor succesive. Limea unei coloane afiare o reprezint cea mai mare din trei valori, fiecare depinznd de metadat&e structura cu informaii privind coloanele: Lungimea cmpului f ield->name, titlul coloanei. f ield->max_length, dimensiunea celei mai lungi valori a datelor din coloan. Lungimea irului" NULL", n cazul n care coloana poate conine valori NULL, arat dac acea coloan poate conine NULL sau nu. Reinei c, dup ce limea de afiare pentru coloan a devenit cunoscut, at aceast valoare variabilei max_length, care este un membru al unei structuri pe obinem din biblioteca client. Acest lucru este permis sau este necesar ca dateiey structura MYSQL_FIELD s fie considerate ca fiind numai pentru citire? n mod nor opta pentru a doua variant, dar unele dintre programele client din distribuia MyS &',*.' " ' ' Capitolul 6 Interfaa AP) MySQL pentru C 281 modific valoarea max_length ntr-un mod asemntor, deci voi presupune c procedeul este permis. (Dac preferai o abordare alternativ, care nu modific max_length, alocai un tablou de valori unsigned int i stocai limile calculate n tabloul respectiv.) Calculul limilor de afiare ascunde o capcan. V mai reamintii c max_length nu are nici o semnificaie atunci cnd creai un set de rezultate folosind mysql_use_result(). Deoarece avem nevoie de max_length pentru a determina limea de afiare a valorilor din coloan, funcionarea corect a algoritmului impune ca setul de rezultate s fie generat folosind mysql_store_result( )2. Odat cunoscute limile coloanelor, suntem gata de afiare. Titlurile sunt uor de manipulat; pentru o coloan dat, folosim pur si simplu structura cu informaii despre coloan indicat prin field i afim membrul name, folosind limea calculat anterior: printf (" %-*s |", field->max_length, field->name); Pentru date, vom parcurge ciclic rndurile din setul de rezultate, afind valorile coloanelor pentru rndul curent n timpul fiecrei iteraii. Afiarea valorilor din coloanele unui rnd este oarecum ciudat, deoarece o valoare poate fi NULL sau poate reprezenta un numr (caz n care l afim aliniat la dreapta). Valorile coloanelor sunt afiate dup cum urmeaz, unde row [ii conine valorile datelor, iar field indic spre informaiile despre coloane: if (row[i] = = NULL) printf (" %-*s |", field->max_length, "NULL"); else if (IS_NUM (field->type) ) printf (" %*s |", field->max_length, row[i]); else printf (" %-*s l", field->max_length, row[il); Valoarea macroinstruciunii IS_NUM() este adevrat dac tipul coloanei indicat de field ->type este un tip numeric, precum INT, FLOAT sau DECIMAL. Liniile de program finale pentru afiarea setului de rezultate se prezint astfel. Reinei c, deoarece afim de mai multe ori linii compuse din mici liniue, codul pentru aceast operaie este ncapsulat n propria sa funcie, print_dashes ( ) : void print_dashes (MYSQL_RES *res_set) MYSQL_FIELD unsigned int *field; i, j; mysql_field_seek (res_set, 0); f pute ('*', stdout) ; for (i = 0; i < mysql_num_fields (res_set); i {

field = mysql_fetch_field (res_set); Continuare 1 Membrul length al structurii MYSQL_FIELD v indic lungimea maxim pe care o pot avea valorile din coloane. Aceasta poate fi o soluie util dac folosii mysql_use_result() n locul funciei mysql_store_result().N.A. 282 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare for (j = 0; j < field->max_length + 2; j +-*) fputcC-', stdout); f pute (' + ', stdout); } f pute ( '\n' , stdout); void process_result_set (MYSQL *conn, MYSQL_RES *res_set) MYSQL_FIELD MYSQL_ROW unsigned *field; row int i, col, len; /* determina limile de afiare ale coloanelor */ mysql_field_seek (res_set, 0); for (i = 0; i < mysql_num_fields (res_set); i field = mysql_fetch_field (res_set); col_len = strlen (f ield->name) ; if (col_len < field->max_length) col_len = field->max_length; if (col_len < 4 && !IS NOT NULL (field->flags)) col_len =4; /* 4 este lungimea cuvntului "NULL" */ field->max_length = col_len; /* reinitializarea informaiilor despre coloana */ print_dashes (res_set) f pute ('l1, stdout); mysql_field_seek (res_set, 0); for (i = 0; i < mysql_num_fields (res_set); field = mysql_fetch_field (res_set); printf (" %-*s |", field->max_length, field->name) ; } f pute ('\n', stdout); print_dashes (res_set); while ((row = mysql_fetch_row (res_set)) 1= NULL) { mysql_field_seek (res_set, 0); f pute (' l' i stdout); Capitolul 6 Interfaa API MySQL pentru C 283 for (i = 0; i < mysql_num_fields (res_set); field = mysql_fetch_field (res_set); if (row[i] = = NULL) printf (" %-*s I", field->max_length, "NULL"); else if (IS_NUM (field->type)) printf (" %*s |", field->max_length, row[i]); else printf (" %-*s |", field->max_length, row[i]); } fputc ('\n', stdout); } print_dashes (res_set); printf ("%lu rnduri returnate\n", (unsigned long) mysql_num_rows (res_set)); } Biblioteca client MySQL furnizeaz numeroase modaliti de acces la structurile cu informaii despre coloane. De exemplu, programul din exemplul anterior obine de mai multe ori accesul la aceste structuri, folosind cicluri avnd urmtoarea form general: mysql_field_seek (res_set, 0); for (i = 0; i < mysql_num_fields (res_set); { field = mysql_fetch_field (res_set); Cu toate acestea, combinaia dintre funciile mysql_f ield_seek () si mysql_f etch_f ield () este numai una din modalitile de a obine structurile MYSQL_FIELD. Pentru a afla despre alte modaliti de obinere a structurilor cu informaii despre coloane, vezi informaiile aferente funciilor mysql_fetch_f ields() i mysql_f etch_f ield_direct() din Anexa F. Client 5 - Un program interactiv de interogare S adunm o buna parte din programele create pn acum si s le folosim pentru a scrie un program client interactiv simplu. Acesta v permite s introducei interogri, le execut folosind rutina noastr de uz general

pentru prelucrarea interogrilor process_query() i afieaz rezultatele folosind programul de formatare a datelor de ieire process_result_set (), creat n seciunea precedent. clients va fi similar din multe puncte de vedere cu mysql, dei nu va avea, desigur, un numr att de mare de caracteristici. Exist numeroase restricii cu privire la datele de intrare care vor fi acceptate de clients: * Fiecare linie de intrare trebuie s conin o singur interogare complet. * Interogrile nu trebuie s se ncheie cu punct i virgul sau cu \g. * Comenzi precum quit nu sunt recunoscute; n schimb, folosii Control-D pentru a termina execuia programului. 284 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Se observ c redactarea programului clients este aproape banal (sub 10 linii de pi gram noi). Aproape toate elementele necesare sunt furnizate.de scheletul prograrm) client (client4.c) si de alte programe pe care le-am scris deja. Singurul lucru care trebuie adugat este un ciclu care adun datele de intrare si le execut. Pentru a construi programul clients, ncepei prin a copia scheletul de program < client4.c n programul clients.c. Apoi adugai la acesta liniile de program afere funciilor process_query(), process_result_set() i print_dashes(). n final, n p*i gramul clients. c, cutai linia din funcia main () care are urmtorul coninut: /* aici se insereaz codul aplicaiei propriu-zise */ Apoi nlocuii-o cu urmtorul ciclu while: while (1) char buf[1024]; fprintf (stderr, "query> "); if (fgets (buf, sizeof (buf); break; process_query (conn, buf); /* prompt de afiare */ stdin) = = NULL) /* citete interogarea */ /* executa interogarea */ Compilai programul clients.c pentru a produce fiierul clients.o, legai clienii cu common.o i cu biblioteca client pentru a genera fiierul clients i ai terminat! Aw un program client MySQL interactiv, care poate executa orice interogare si afi rezultatele. Alte aspecte Aceast seciune trateaz numeroase subiecte care nu s-au putut ncadra foarte bit evoluia de la clieni la clients: ' Utilizarea datelor din setul de rezultate pentru a calcula un rezultat, dup utilia metadatelor din setul de rezultate pentru a contribui la verificarea faptului c sunt adecvate pentru calculul dumneavoastr. Modul de tratare a datelor dificil de inserat n interogri. Modul de lucru cu datele de tip imagine. Modul de obinere a informaiilor referitoare la structura tabelelor dumneavoas Greeli comune de proiectare n MySQL i modul de evitare a acestora. Efectuarea de calcule cu seturile de rezultate Pn acum, ne-am concentrat asupra utilizrii metadatelor aferente seturilor de rezul mai ales pentru afiarea datelor din rnduri, dar este evident c vor exista situaii " datele dumneavoastr vor trebui folosite i n alte moduri, nu numai afiate. De exti putei efectua calcule statistice n funcie de valorile datelor, folosind metadatele v asigura c datele se conformeaz cerinelor pe care dorii ca acestea s le satisfa

Capitolul 6 Interfaa API MySQL pentru C 285 fel de cerine? nceptorii vor dori probabil s verifice dac o coloan cu al crei coninut intenioneaz s efectueze calcule numerice conine cu adevrat numere! Listingul urmtor prezint o funcie simpl, denumit summary_stats( ), care preia un set de rezultate i un index de coloan i produce statistici de sumar pentru valorile din coloan. De asemenea, funcia raporteaz numrul de valori care lipsesc, pe care le detecteaz cutnd valorile NULL. Aceste calcule implica dou cerine pe care trebuie s le satisfac datele, deci summary_stats( ) le verific folosind metadatele aferente setului de rezultate: Coloana specificat trebuie s existe (adic indexul coloanei trebuie s se gseasc n domeniul care conine numrul de coloane din setul de rezultate). Coloana trebuie s conin valori numerice. Dac aceste condiii nu sunt valabile, summary_stats() pur i simplu afieaz un mesaj de eroare i returneaz. Liniile de program sunt urmtoarele: void VOJ.U summary_stats (MYSQL_RES *res_set, unsigned int col_num) MYSQL_FIELD*camp; MYSQL_ROW rand; unsigned int n,lipsa; double val, suma, suma_patrate, var;

/* verifica ndeplinirea cerinelor pentru date */ if (mysql_num_fields (res_set) < coljium) { print_error (NULL, "numr de coloana incorect"); return; } mysql_field_seek (res_set, 0); field = mysql_fetch_field (res_set); if (!IS_NUM (field->type)) { print_error (NULL, "coloana nu este numerica"); return; /* calculeaz statisticile de sumar */ n = 0; lipsa = 0; suma = 0; suma_patrate = 0; Continuare 286 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare mysql_data_seek (res_set, 0); while ((rand = mysql_fetch_row (res_set)) != NULL) if (rand[col_num] == NULL) lipsa++; else { val = atof (rand[col_num]); /* conversie din sir in numr,* suma += val; suma_patrate += val * val; if (n = = 0) printf ("Nu sunt observation"); else printf ("Numr de observaii: %lu\n", n); printf ("Observaii lipsa: %lu\n", lipsa); printf ("Suma: %g\n", suma); printf ("Media: %g\n", suma / n); printf ("Suma ptratelor: %g\n", suma_patrate); var = ((n * suma_patrate) - (suma * suma)) / (n printf ("Dispersie: %g\n", var); printf ("Abatere standard": %g/n, sqrt (var)); (n - 1)); Observai apelul la funcia mysql_data_seek() care precede ciclul fura mysql_f etch_loop(). Apelul respectiv are rolul de a v permite s apelai funcia sul ry_stats () de mai multe ori, pentru a obine acelai set de rezultate (n caz c don calculai date statistice pentru mai multe coloane). La fiecare apelare a funciei sul ry_stats(), aceasta d napoi" pn la nceputul setului de rezultate. (Acest fapt| supune c ai creat setul de rezultate cu mysql_store_result(). Dac l ere mysql_use_result(), putei prelucra rndurile numai n ordine i numai o dat.); summary_stats() este o funcie relativ simpl, dar ar trebui s v ofere o idee cu [_ la modul n care putei programa calcule mai complexe, cum este regresia celor ma|| ptrate pe dou coloane sau statistici standard, cum este un test t. Codificarea n interogri a datelor problematice Valorile de date care conin zerouri, ghilimele sau backslash-uri, dac sunt inserat ral ntr-o nregistrare, pot cauza probleme atunci cnd ncercai s executai inter Expunerea urmtoare prezint natura dificultii si modul de rezolvare a acesteia, l *&. v Capitolul 6 Interfaa API MySQL pentru C 287 S presupunem c dorii s construii o interogare SELECT n funcie de coninutul irului ncheiat prin zero i indicat prin nume: char query[1024]; sprintf (query, "SELECT * FROM tabel WHERE nume='%s'", nume); Dac valoarea lui nume este ceva de genul "O'Malley, Brian", interogarea rezultat este incorect, deoarece n cadrul unui ir delimitat prin ghilimele apar alte ghilimele: SELECT * FROM tabel WHERE nume='O'Malley, Brian1 Trebuie s tratai ghilimelele ntr-un mod aparte, astfel nct serverul s nu le interpreteze ca fiind sfritul numelui. O modalitate const n dublarea ghilimelelor din interiorul irului. Aceasta este convenia ANSI pentru SQL. MySQL nelege aceast convenie si mai permite ca ghilimelele s fie precedate de un backslash: SELECT * FROM tabel WHERE nume='0"Malley, Brian1 SELECT * FROM tabel WHERE nume='0\'Malley, Brian1 O alt situaie problematic implic utilizarea ntr-o interogare a unor date binare arbitrare. Acest lucru se ntmpl, de exemplu, n aplicaii care stocheaz imagini ntr-o baz de date. Deoarece o valoare binar poate conine orice caracter, nu poate fi inclus ca atare ntr-o interogare, n condiii de siguran.

Pentru a rezolva aceast problem, folosii mysql_escape_string(), care codific acele caractere speciale, pentru a permite utilizarea lor n irurile delimitate prin ghilimele. Caracterele pe care funcia mysql_escape_string () le consider speciale sunt caracterul nul, ghilimelele simple i duble, backslash-ul, linia nou, returul de car si Control-Z. (Ultimul apare n contexte Windows.) Cnd trebuie s folosii mysql_escape_string()? Rspunsul cel mai sigur este ntotdeauna". Totui, dac suntei sigur de forma datelor dumneavoastr si tii c totul este n ordine - poate deoarece ai efectuat anterior verificri pentru validare - nu trebuie s le codificai. De exemplu, dac lucrai cu iruri despre care tii c reprezint numere de telefon corecte, alctuite integral din cifre si liniue, nu trebuie s apelai mysql_escape_string(). n caz contrar, probabil c trebuie s apelai funcia respectiv. mysql_escape_string() codific acele caractere problematice transformndu-le n secvene de dou caractere care ncep cu un backslash. De exemplu, un octet nul devine \0, unde O este un caracter O ASCII care poate fi afiat, nu un caracter nul. Caracterele backslash, ghilimelele simple i duble se transform n \ \, \' i \". Pentru a folosi funcia mysql_escape_string(), invocai-o astfel: toJLen = mysql_escape_string (to_str, from_str, from_len); mysql_escape_string() codific irul f rom_str i scrie rezultatul n to_str. De asemenea, adaug un zero de final, care este convenabil deoarece putei folosi irul rezultant cu funcii precum strcpy () si strlen (). f rom_str indic spre un buffer de tip char, care conine irul ce trebuie codificat. Acest ir poate conine orice, inclusiv date binare. to_str indic spre un buffer char existent, unde dorii s fie scris irul codificat; nu transferai un pointer neiniializat sau NULL, ateptnd ca funcia mysql_escape_string() s aloce spaiu n mod automat. Lungimea bufferului indicat de to_str trebuie s fie de cel puin (f rom_len*2) +1 octei. (Este posibil ca fiecare caracter din f rom_str s necesite codificarea cu dou caractere; octetul suplimentar este destinat zeroului de final.) 288 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL f rom_len i to_len sunt valori de tipul unsigned int. f rom_len indic lungimea dat| din f rom_str; este necesar furnizarea lungimii, deoarece f rcm_str poate conine < nuli i nu poate fi tratat drept un ir terminat n zero. to_len, valoarea returaat$|3 funcia mysql_escape_string (), este lungimea efectiv a irului codificat rezultant a numra zeroul final. Cnd funcia mysql_escape_string() returneaz, rezultatul codificat n to_str j tratat ca un ir care se termin n zero, deoarece toate zerourile din f rom_str suntxj ficate sub forma secvenei afiabile \0. Pentru a rescrie codul de construcie al instruciunii SELECT astfel nct s funci chiar si pentru nume care conin ghilimele, vom proceda astfel: char query[1024], *p; p = strcpy (query, "SELECT * FROM tabel WHERE nume="); p += strlen (p); p += mysql_escape_string (p, nume, strlen (nume)); p = strcpy (p, '""); Da, e urt. Dac dorii s simplificai puin, cu preul utilizrii unui al doilea buffet, j cedai astfel: char query[1024], buf[1024]; (void) mysql_escape_string (buf, nume, strlen (nume)); sprintf (query, "SELECT * FROM tabel WHERE nume='%s", buf); Lucrul cu date sub form de imagini Una dintre sarcinile pentru care funcia mysql_escape_string() este esenial ifli ncrcarea datelor sub form de imagine ntr-un tabel. Aceast seciune v arat i procedai. (Expunerea de fa este valabil i pentru orice form de date binare.) S presupunem c dorii s citii imagini din fiiere si s le stocai ntr-un tabel," de un identificator unic. Tipul BLOB reprezint o opiune bun pentru datele bina putei folosi o specificaie de tabel ca aceasta: CREATE TABLE imagini imagine_id INT NOT NULL PRIMARY KEY, imagine_data BLOB Pentru a v procura efectiv o imagine dintr-un fiier situat n tabelul imagini,, urmtoare, incarca_imagine(), execut aceast operaie, date fiind un numr de|d ficare i un pointer spre un fiier deschis care conine datele n format imagine: int i incarca_imagine (MYSQL *conn, int id, FILE *f) char query[1024*100], buf[1024*10], *p; unsigned int from_len; Capitolul 6 Interfaa API MySQL pentru C 289 int status; sprintf (query, "INSERT INTO imagini VALUES (%d,l-, id); p = query + strlen (query); while ((from_len = fread (buf, 1, sizeof (buf), f)) > 0)

/* nu depi finalul bufferului de interogare! */ if (p + (2*from_len) + 3 > query + sizeof (query)) pritvt_error (NULL, "imagine prea mare"); return (1); p += mysql_escape_string (p, buf, from_len); (void) strcpy (p, "')"); status = mysql_query (conn, query); return (status); incarca_imagine() nu aloc un buffer de interogare foarte mare (100K), deci funcioneaz numai pentru imagini relativ simple, ntr-o aplicaie din lumea real, putei aloca bufferul n mod dinamic, n funcie de dimensiunea fiierului imagine. Manipularea datelor sub form de imagine (sau a oricror date binare) pe care le obinei dintr-o baz de date nu constituie o problem la fel de mare ca inseria lor pentru a putea fi utilizate, deoarece valorile de date sunt disponibile n form brut n variabila MYSQL_ROW, iar lungimile sunt disponibile prin apelarea funciei mysql_f etch_lengths (). Pur i simplu nu uitai s tratai valorile ca iruri numrate, nu ca iruri care se ncheie cu zero. Obinerea informaiilor referitoare la tabele MySQL v permite s obinei informaii despre structura tabelelor dumneavoastr folosind oricare dintre aceste interogri (care sunt echivalente): DESCRIBE nume_tabel SHOW FIELDS FROM nume_tabel Ambele instruciuni seamn cu SELECT, n sensul c returneaz un set de rezultate. Pentru a afla date despre coloanele din tabel, tot ce trebuie s facei este s prelucrai rndurile din rezultat pentru a extrage informaiile de care avei nevoie. De exemplu, dac emitei o instruciune DESCRIBE imagini din clientul mysql, acesta va returna urmtoarea informaie: Field Type Null Key I Default Extra imagine_id int(11) imagine_data blob YES PRI i 0 i NULL Dac executai aceeai interogare din propriul dumneavoastr client, vei primi aceleai iformaii (fr casete). 290 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Dac dorii informaii numai despre o singur coloan, folosii aceast interogare: SHOW FIELDS FROM nume_tabel LIKE "nume_coloana" Interogarea va returna aceleai coloane, dar un singur rnd (sau nici un rnd, n cazul rJ care coloana nu exist). Greeli de programare a programelor client care trebuie evitate Aceast seciune discut despre unele erori comune de programare a interfeelor . pentru MySQL scrise n C, precum i despre modul de evitare a acestora. (Aceste pr bleme apar periodic n lista de coresponden despre MySQL; nu le-am inventat eu.) Greeala 1: Utilizarea unor pointer! neiniializai ai variabilelor de tratare a conexiunilor n exemplele prezentate n acest capitol, am apelat funcia mysql_init() transfernd acesteia un argument NULL. Astfel, se indic funciei mysql_init () s aloce i s inirializ o structur MYSQL i s returneze un pointer spre aceasta. O alt abordare o consrit transferul unui pointer spre o structur MYSQL, n acest caz, mysql_init () va iniializa; structur i va returna un pointer ctre aceasta fr a aloca structura nsi. Dac dorii i folosii aceast a doua metod, aceasta poate duce la anumite probleme dificil de depis Urmtoarea expunere va scoate n eviden unele probleme de evitat. Dac transferai un pointer funciei mysql_init (), acesta trebuie s indice spre ceva...'{ lum aceast component de program: main() MYSQL *conn; mysql_init (conn);

Problema este c mysql_init () primete un pointer, dar pointerul nu indic spre : semnificativ, conn este o variabil local i, implicit, constituie un spaiu de st neiniializat, care poate indica oriunde atunci cnd funcia main () i ncepe execuii Aceasta nseamn c mysql_init () va folosi pointerul si va scrie undeva ntr-o zon aleatoare de memorie. Dac avei noroc, conn va indica undeva n afara spaiului de adrese al programului dumneavoastr i sistemul va termina imediat execuia prograft. mului, deci vei sesiza apariia problemei nc de la primele linii de program. Dac a' suntei att de norocos, conn va indica spre date pe care le folosii mai trziu n program^* iar dumneavoastr nu vei sesiza apariia unei probleme dect atunci cnd programai ncearc efectiv s foloseasc datele respective, n acest caz, problema va prea c se pvoHt' duce ntr-o faz a programului mult mai avansat dect aceea din care provine efectiv i poate fi mult mai dificil de depistat. Iat un program cu probleme" asemntor: MYSQL *conn;

main() Capitolul 6 Interfaa API MySQL pentru C 291 mysql_init(conn); mysql_real_connect mysql_query (conn, (conn, ...) "SHOW DATABASES"); n acest caz, conn este o variabil global, deci este iniializat la O (adic NULL) nainte de pornirea programului. mysql_init () vede un argument NULL, deci se iniializeaz i aloc o nou variabil de tratare a conexiunii. Din pcate, conn este nc NULL, deoarece niciodat nu i este atribuit vreo valoare. De ndat ce transferai conn unei funcii din interfaa API n C pentru MySQL care impune o variabil de tratare a conexiunii diferit de NULL, programul dumneavoastr va cdea. Pentru ambele programe, remediul l constituie asigurarea faptului c variabila conn are o anumit valoare. De exemplu, o putei iniializa la adresa unei structuri MYSQL deja alocate: MYSQL conn_struct, *conn = &conn_struct; mysql_init (conn); Totui, soluia recomandat (i mai uoar!) este pur i simplu de a transfera NULL n mod explicit funciei mysql_init(), de a permite acelei funcii s aloce automat structura MYSQL i s atribuii variabilei conn valoarea returnat: MYSQL *conn; conn = mysql_init (NULL); n orice caz, nu uitai s testai valoarea returnat a funciei mysql_init(), pentru a v asigura c nu este NULL. Greeala 2: Lipsa testului validitii unui set de rezultate Nu uitai s verificai starea apelurilor de unde v ateptai s obinei un set de rezultate. Programul de mai jos nu face asta: MYSQL_RES *res_set; MYSQL_ROW row; res_set = mysql_store_result (conn); while ((row = mysql_fetch_row (res_set)) ! { /* prelucrare rand */ NULL) Din pcate, dac mysql_store_result() eueaz, res_set este NULL si ciclul while nici mcar nu mai trebuie executat. Testai valoarea returnat de funciile care returneaz seturi de rezultate, pentru a v asigura c avei efectiv un material de lucru". Greeala 3: Ignorarea valorilor NULL din coloane Nu uitai s verificai dac valorile coloanelor din tabloul MYSQL_ROW returnat de ctre funcia mysql_fetch_row() sunt pointeri NULL. Pe unele calculatoare, programul urmtor cade" dac row[i] este NULL:

292 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL for (i = 0; i < mysql_num_fields (res_set); i++) { if (i > 0) fputc ('\t', stdout); printf ("%s", row[i]); fputc ('\n', stdout); Partea cea mai grav a acestei greeli este c unele versiuni ale funciei printf () sunt ia! toare" si afieaz " (nuli)" pentru pointeri NULL, ceea ce v permite s scpai fr a re dia problema. Dac oferii programul dumneavoastr unui prieten care dispune de o i printf () mai puin mrinimoas, programul cade i prietenul dumneavoastr ajut concluzia c suntei un programator de doi bani. Ciclul de mai sus ar fi trebuit scris; for (i = 0; i < mysql^num_fields (res_set); if (i > 0) fputc ('\t', stdout); printf ("%s", row[i] 1= NULL ? row[i] : "NULL"); fputc ('\n', stdout); Singura dat cnd nu trebuie s verificai dac valoarea dintr-o coloan este NULL i atunci cnd ai determinat deja, din structura de informaii privind coloana, c func IS_NOT_NULL() are valoarea adevrat". Greeala 4: A transfera buffere de rezultat lipsite de sens Funciile din biblioteca client care se ateapt ca dumneavoastr s furnizai buffere general pretind ca aceste buffere s si existe. Programul de mai jos ncalc acest prii char *from_str = "un ir"; char *to_str;

unsigned int len; len = mysql_escape_string (to_str, from_str, strlen (from_str)); Care este problema? to_str trebuie s indice spre un buffer existent, n acest exemwl nu o face; indic spre o locaie aleatoare. Nu transferai un pointer heiniializat ca > ment to_str al funciei mysql_escape_string() dect dac dorii ca aceasta si.j. mpiedice cu entuziasm peste cine tie ce zon de memorie aleatoare. CAPITOLUL 7 Interfaa API pentru Perl DBI Capitolul de fa prezint modul de utilizare a interfeei Perl DBI pentru MySQL. Nu este discutat filozofia sau arhitectura DBI. Pentru informaii privind aceste aspecte ale DBI (mai ales n comparaie cu interfeele API n C i PHP), consultai capitolul 5, Introducere n programarea MySQL". Exemplele din acest capitol pornesc de la baza noastr de date demonstrativ, si anume samp_db, folosind tabelele necesare pentru proiectul de eviden a rezultatelor colare, respectiv pentru Liga istoric. Pentru a utiliza la maximum acest capitol, trebuie s tii unele lucruri despre Perl. Dac nu, v putei scrie propriile dumneavoastr scripturi prin simpla copiere a programului demonstrativ prezentat aici. Dar probabil c o carte bun de Perl va constitui o investiie solid pentru dumneavoastr. O asemenea carte este Programming Perl, Second Edition, de Wall, Christiansen, Schwartz i Potter (O'Reilly, 19%). n prezent, DBI se afl la versiunea 1.13, dei majoritatea celor prezentate aici se aplic i versiunilor anterioare l.xx. Caracteristicile descrise n capitolul de fa i care nu exist n versiunile anterioare au fost menionate n acest sens. DBI pentru MySQL necesit o versiune de Perl cel puin la fel de recent ca versiunea 5.004_05. De asemenea, trebuie s instalai Msql-Mysql-modules i modulele Perl Data-Dumper, precum i fiierele antet i biblioteca client C pentru MySQL. Dac intenionai s scriei scripturi DBI bazate pe Perl, probabil c vei dori s folosii modulul CGI.pm. n acest capitol, modulul respectiv este folosit n conjuncie cu serverul Web Apache. Dac trebuie s v procurai oricare dintre aceste pachete, vezi Anexa A, Obinerea i instalarea programelor". Instruciunile pentru procurarea scripturilor cu caracter de exemplu prezentate n acest capitol sunt de asemenea date n anexa respectiv. Putei descrca scripturile, pentru a evita s le tastai personal. n majoritatea situaiilor, acest capitol descrie metodele i variabilele Perl DBI numai n msura n care acestea sunt necesare pentru expunerea de faa. Pentru o list comprehensiv a tuturor metodelor i variabilelor, vezi Anexa G, Referin API Perl DBF. Putei folosi anexa respectiv ca referin pentru alte informaii privind orice component DBI pe care ncercai s o folosii. Prin rularea urmtoarelor comenzi se poate obine accesul la documentaia electronic: % perldoc DBI % perldoc DBI::FAQ % perldoc DBD::mysql V.i ni fi: l t| i > (*<' r ', ; 294 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL La nivelul driverului de baze de date (DBD), driverul pentru MySQL este construit peste biblioteca client C pentru MySQL i ca atare mprumut unele dintre caracteristicile acesteia. Vezi capitolul 6, Interfaa API MySQL pentru C", pentru mai multe informaii despre biblioteca respectiv. * "l !'' ' : Caracteristici ale scripturilor Perl Scripturile Perl nu sunt altceva dect fiiere text, iar dumneavoastr le puteri crea folosind orice editor de texte. Toate scripturile Perl din acest capitol respect convenia UNIX de a utiliza o prim linie care ncepe cu #!, urmat de numele de cale al programului folosit pentru executarea scriptului. Linia pe care o utilizez eu este urmtoarea: #! /usr/bin/perl Va trebui s modificai linia #! dac numele cii de acces spre Perl este diferit n sistemul dumneavoastr, de exemplu /usr/local/bin/perls sau /opt/bin/Perl. n caz contrar, scripturile Perl nu vor rula corect n sistemul dumneavoastr. Am inclus un spaiu dup secvena #!,. deoarece unele sisteme interpreteaz secvena #! / ca pe un numr magic din 4 octeti, ignor linia dac spaiul lipsete si astfel trateaz scrip-tul ca pe un script de interpretor. Sub UNIX, un script Perl trebuie s fie executabil, pentru a putea fi rulat prin simpla tastare a numelui su. Pentru a face un fiier script executabil, modificati-i modul fiier dup cum urmeaz: r ' -- : " t % chnod +x nume_script . Dac folosii ActiveState Perl sub ^Endows, nu facei scripturile executabile, n schimb, rulai un script ca acesta: C:\> perl nume_script

', '!:,<.: .r f " Elemente fundamentale ale modulului Perl DBI Aceast seciune furnizeaz informrii elementare despre Perl DBI, n spe informaiile de care avei nevoie pentru a yi .scrie propriile scripturi si pentru a nelege scripturile scrise de altii. Dac suntei un cunosctor n materie.de DBI, putei trece direct la seciur nea Utilizarea DBI". ,,..,. Tipuri de date DBI Din anumite puncte de vedere, utilizarea interfeei AI*I pentru Perl DBI este similar ce utilizarea bibliotecii client G descris n capitolul & Cnd folosiri biblioteca client G, apelai funcii si obineri acces k dat legate de MySQL mai ales prin mtermediul pointe*-rilor ctre structuri sau ctre tablouri. Cnd folosiri interfaa API pentru DBI, conti s apelai funcii si s folosiri pointeri la structuri, cu excepia faptului c.funciile numesc metode, pointerii se numesc referine, variabilele pointer se aamescMariabile manipulare, iar structurile spre care indic variabilele de manipulare se numesc obiecte. DBI folosete numeroase categorii de variabile de manipulare, n documentaia D acestea au tendina de a fi specificate prin numele convenionale prezentate n tabelul 7. Capitolul? Interfaa API penWiPert>DBf 295 Modalitatea n care folosii aceste variabile va deveni evident pe parcurs. De asemenea, sunt folosite numeroase nume convenionale pentru variabile, altele dect variabilele de manipulare (vezi tabelul 7.2). Nu vom folosi toate aceste nume de variabile n capitolul de fa, dar este util s le cunoatei atunci cnd citii scripturi DBI scrise de alte persoane. Tabelul 7.1 Nume convenionale ale variabilelor de manipulare folosite n Peri I>BI Nume $dbh $sth $fh $h Semnificaie Variabil de manipulare a unui obiect baz de date - Variabil de manipulare a unui obiect instruciune{interogare) Variabil de manipulare a unui fiier deschis i.j! :i Variabil de manipulare general"; semnificaia depinde de coninut Tabelul 72 Nume convenionale ale variabilelor, altele dect variabilele de manipulare folosite n Perl DBI -:, ry Nume $PC $rv $rows ary Semnificaie Codul retumat de operaii care retumeaz adevraf sau .fals" ' Valoarea retumat de operaii care retumeaz un ntreg Valoarea retumat de operaii care retumeaz un numr de rnduri Un tablou (list) care reprezint un rnd de valori i Un script DBI simplu S ncepem de Ia un script simplu, dumpjnembers, care ilustreaz numeroase concepte standard' ae programrii DBI, cum ar fi conectarea i deconectarea de ta serverul MySQL, emiterea interogrilor i regsirea datelor. Acest script propice drept rezultat lista membrilor Ligii istorice, ntr-un format cu datele deKnuitate priiu tabuaiori. Formatul' nu este interesant n sine; n acest moment, este mai nnportant s vedem cum se folosete DBI dect s generm date de ieire cu un aspect estetic. '' ' ' " Scriptul duiapjnembers se prezint astfel: , , . ,#1 Atr/ bin /perl . ,: , ' , i, , -< < * # duwp members - afieaz lista membrilor Ligii istorice ~ ' .. . t j use DBI; ** use strict; ' my (^dsn) = "D&l:mysql:samp_<lb:localhost'lV * nume sursa- date y if$oser_Bae) = 'paul; # numele utilizate ru%|^ >' '-y <^passv)rd) = 'secret" # parola r^"'v ^ my ($dbh, $sth); . # variabile de date s .ne Jsza

hi 296 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Continuare my (@ary); tablou pentru rndurile returnate de interogare # conectarea la baza de date $dbh = DBI->connect ($dsn, $user_name, Spassword, { RaiseError => 1 }); # emiterea interogrii $sth = $dbh->prepare ("SELECT nume, prenume, sufix, email," . "strada, ora, stat, cod_postal, telefon FROM membru . "ORDER BY nume"); $sth->execute (); # citete rezultatele interogrii, apoi face curenie while (@ary = $sth->fetchrow_array ()) { print join ("\t", @ary), "\n"; } $sth->finish (); $dbh->disconnect (); exit (0); Pentru a testa personal scriptul, fie fl descrcri (vezi Anexa A), fie l creai folosind un editor de texte i apoi l faceri executabil pentru a-1 putea rula. Probabil c va trebui s modificri mcar o parte din parametrii de conexiune (numele gazdei, numele bazei de date, numele de utilizator, parola). Acest fapt este valabil i pentru celelalte scripturi DBI din capitolul de fa. n mod prestabilit, permisiunile pentru scripturile din acest capitol care pot fi descrcate din Internet sunt astfel configurate nct scripturile pot fi citite numai de dumneavoastr. V propun s nu modificri aceast proprietate, dac v inserai propriul nume de utilizator MySQL i propria parol, pentru ca aceste valori s nu poat fi citite de alte persoane. Mai trziu, n seciunea Specificarea parametrilor de conexiune", vom vedea cum se pot obine parametrii dintr-un fiier cu opiuni, n loc de a-i insera direct n script. Acum, s parcurgem scriptul instruciune cu instruciune. Prima linie conine indicatorul standard de localizare a limbajului Perl: #! /usr/bin/perl Aceast linie face parte din toate scripturile pe care le vom discuta n capitolul de fa; nu vom mai vorbi despre ea n viitor. Este o idee bun s includei n script cel puin o descriere minimal a scopului su, deci linia urmtoare este un comentariu care furnizeaz oricrei persoane care examineaz scriptul o idee asupra finalitii acestuia: # dumpjnembers - afieaz lista membrilor Ligii istorice Textul cuprins ntre caracterul # i pn la sfritul liniei se consider comentariu. Merit s inserai comentarii n scripturi, comentarii care explic modul de funcionare a acestora. fii*

Capitolul 7 Interfaa API pentru Perl DBI n continuare, avem dou linii use: use DBI; use strict;

297

use DBI comunic interpretorului Perl c trebuie s foloseasc modulul DBI. n lipsa acestei linii, se va produce o eroare de ndat ce ncercai s ntreprindei orice aciune legat de DBI. Totui, nu trebuie s indicai modulul de nivel DBD dorit. DBI activeaz automat modulul corect atunci cnd v conectai la baza dumneavoastr de date. use strict indic programului Perl s v oblige s declarai variabilele nainte de a le folosi. Putei scrie scripturi fr a insera o linie use strict, dar aceasta este util pentru detectarea greelilor, deci v recomand s o folosii ntotdeauna. De exemplu, cnd modul strict este activat, dac declarai o variabil $my_var, dar apoi facei referire la variabila respectiv n mod eronat, sub numele $mv_var, veri primi urmtorul mesaj atunci cnd rulai scriptul: Global symbol "$mv_var" requires explicit package name at line n (Simbolul global $mv_var" impune un nume explicit de pachet la linia n) Cnd vedei aa ceva, v spunei: Ce? $mv_var? N-am folosit niciodat o variabil cu numele acela!" Apoi v examinai linia n din script, depistai problema i o remediai, n lipsa modului strict, Perl nu va ridica obiecii n ceea ce privete $mv_var; pur si simplu creeaz o variabil cu numele respectiv si de valoare undef (de la undefined - nedefinit), o folosete fr s fac nazuri, iar dumneavoastr v ntrebai de ce oare nu merge scriptul. Deoarece lucrm n modul strict, vom declara variabilele pe care le folosete scriptul: my ($dsn) = "DBI:mysql:samp_db:localhosta; # nume sursa date my ($user_name) = "paul"; # numele utilizatorului my ($password) = "secret" # parola my (Sdbh, $sth); # variabile de manipulare pentru baza de date si instruciune my (@ary); # tablou pentru rndurile returnate de interogare Acum, suntem pregtii s ne conectm k baza de date: # conectarea la baza de date $dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1 }); Apelul la funcia connect() este invocat sub forma DBI->connect(), deoarece aceast funcie este o metod a clasei DBI. Nu trebuie s tii exact ce nseamn asta; nu este nimic altceva dect o mic mostr de jargon orientat spre obiecte, aa, ca s v doar capul. (Dac vrei s tii cu tot dinadinsul, nseamn c funcia connect () este o funcie care aparine" modulului DBI.) Funcia connect() preia mai multe argumente: Sursa de date. (Denumit deseori numele sursei de date sau DSN - abreviere de la data source name.) Formatele surselor de date sunt determinate de cerinele modulului DBD pe care dorii s-1 folosiri. Pentru driverul MySQL, formatele permise includ oricare din urmtoarele variante: "DBI:mysql:nume_baza_de_date" "DBI:mysql:nume_baza_de_date:nume_gazda" ** "* 298 Partea a ll-a Utilizarea interfeelor de programare ale sistemului MySQL Pentru primul format, numele de gazd prestabilit este localhost. (Exist i alte formate permise pentru sursele de date, despre care vom discuta ulterior n seciunea Specificarea parametrilor de conexiune") Nu conteaz mrimea literelor folosite n "DBI", dar "mysql" trebuie scris cu litere mici. Numele dumneavoastr de utilizator i parola. Un argument opional, care indic atribute de conexiune suplimentare. Acesta controleaz modalitatea DBI de tratare a erorilor, iar construcia cu aspect ciudat pe care a*n specificat-o activeaz atributul RaiseError. Aceasta determin modulul DBI s caute erorile relative la bazele de date, respectiv s afieze un mesaj i s-i ncheie execuia ori de cte ori ntlnete o eroare. (Iat de ce nu vedei nici un program de detecie a apariiei erorilor nicieri n interiorul scriptului dumpjnembers; DBI se ocup de aceast operaie.) Seciunea Tratarea erorilor" discut despre metode alternative de reacie la apariia unor erori. n cazul n care apelul la funcia connect () reuete, returneaz o variabil de manipulare pentru baze de date, pe care o atribuim lui $dbh. (Dac apelul la connect () eueaz, n mod normal returneaz undef. Totui, deoarece am activat RaiseError n scriptul nostru, connect () nu va returna; n schimb, DBI va afia un mesaj de eroare i i va ncheia execuia n cazul apariiei unei erori.) Dup conectarea la baza de date, dumpjnembers emite o interogare SELECT pentru a regsi lista membrilor, apoi execut un ciclu pentru a prelucra fiecare din rndurile returnate. Aceste rnduri constituie setul de rezultate. Pentru a efectua o interogare SELECT, mai nti o pregtii i apoi o executai: # emiterea interogrii $sth = $dbh->prepare ("SELECT nume, prenume, sufix, email," . 'strada, ora, stat, cod_postal, telefon FROM membru" " ORDER BY nume"); $sth->execute (); Funcia prepa re () este apelat folosind variabila de manipulare pentru baze de date; aceasta transfer driverului instruciunea SQL pentru pre-procesare anterior execuiei. Unele drivere utilizeaz efectiv instruciunea n acest

punct. Altele i reamintesc de instruciune doar atunci cnd invocai execute () pentru a determina efectuarea instruciunii. Valoarea returnat de prepare () este o variabil de manipulare pentru instruciuni, adic $sth, respectiv undef n cazul apariiei unor erori. Variabila de manipulare pentru instruciuni este folosit pentru toate prelucrrile ulterioare legate de instruciune. ' Reinei c interogarea este specificat fr caracterul de terminare punct i virgula. Categoric, avei obiceiul (dezvoltat dup un timp ndelungat de lucru cu programul mysql) de a termina instruciunile SQL cu un caracter ;. Totui, cel mai bine este s v dezbrai de acest obicei atunci cnd folosii DBI, deoarece deseori caracterele punct si virgul determin eecul interogrilor datorit unor erori de sintax. Aceeai reguli se aplic i pentru caracterele \g: nu le adugai la sfritul interogrilor! Capitolul 7 Interfaa API pentru Perl DBI 299 Cnd invocai o metod fr a-i transfera nici un argument, putei omite parantezele. Urmtoarele dou apeluri sunt echivalente: $sth->execute (); $sth->execute; Eu prefer s folosesc parantezele, deoarece astfel apelul seamn mai puin cu o referin la o variabil. Poate c preferinele dumneavoastr sunt altele. Dup ce apelai funcia executed, rndurile din lista cu membri pot fi prelucrate, n scriptul duwpjnembers, ciclul de preluare a rndurilor pur si simplu afieaz coninutul fiecrui rnd: # citete rezultatele interogrii, apoi face curenie while (@ary = $sth->fetchrow_array()) { print join ("Vf, @ary), "Vn" > $sth->finish (); Funcia fetchrow_array{) returneaz un tablou care conine valorile coloanelor din rndul curent, respectiv un tablou vid atunci cnd nu mai exist rnduri. Astfel, ciclul preia rndurile succesive returnate de instruciunea SELECT i l afieaz pe fiecare, insernd tabulatori ntre valorile coloanelor. Valorile NULL din baza de date sunt returnate scriptului Perl ca valori undef, dar acestea sunt afiate ca iruri vide, nu sub forma cuvntului NULL. Observai includerea tabulatorilor i a caracterelor linie nou (reprezentate sub forma '\t' respectiv '\n') ntre ghilimele duble, n Perl, secvenele escape sunt interpretate numai cnd apar ntre ghilimele duble, nu ntre ghilimele simple. Dac au fost folosite ghilimelele simple, rezultatul va fi plin de inctane literale ale irurilor " \t" i" \n". Dup terminarea ciclului de preluare a rndurilor, apelul la funcia f inish () indic programului DBI c variabila de manipulare pentru instruciuni nu mai este necesar i c pot fi eliberate toate resursele temporare asociate acesteia. Apelarea funciei finish () nu este necesar dect dac ai preluat numai o parte din setul de rezultate (prin proiectare sau datorit apariiei anumitor probleme). Totui, f inish {) poate fi ntotdeauna utilizat n siguran dup un ciclu de preluare i mi se pare mai uor s apelai funcia i s o utilizai dect s scriei logica necesar pentru a disinge ntre situaiile n care finish () este necesar de cele n care funcia respectiv este superflu. Dup ce am afiat lista membrilor am terminat, deci ne putem deconecta de la server si ncheia execuia programului: $dbh->disconnect (); exit (0); Scriptul dumpjnembers ilustreaz un numr de concepte care sunt comune majoritii programelor DBI, iar dumneavoastr putei ncepe probabil s v scriei propriile programe DBI fr a avea alte cunotine suplimentare. De exemplu, pentru a scrie coninutul unui alt tabel, tot ce avei de fcut este s modificai textul instructiunii SELECT care este transferat metodei prepare () De fapt, dac dorii s vedei unele aplicrii ale acestei tehnici, putei trece imediat la acea parte a seciunii Utilizarea DBF care discut

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