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 ntr-o
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 John i Braintree MA
Adams John Quincy ' Braintree MA
Arthur Chester A. ; Fair-field VT
Buchanan James i Mercersburg PA
Bush George W. ; Milton MA
Carter James E. , Jr. Plains GA
Cleveland Grover i Caldwell 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 297


n continuare, avem dou linii use:
use DBI;
use strict;
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