Documente Academic
Documente Profesional
Documente Cultură
Versiunea modificată a funcţiei do_connect () este similară cu versiunea anterioară^ toate punctele de vedere, cu
două excepţii:
• Nu transferă un parametru db_name formei mai vechi a funcţiei mysql_real_c(j nect (), deoarece versiunea
respectivă nu dispune de un atare parametru.
• Dacă numele bazei de date nu este NULL, funcţia do_connect () ape funcţia mysql_select_db() pentru a
transforma baza de date denumit bază de date curentă. (Acest procedeu simulează efectul paramet db_name, care
lipseşte.) Dacă baza de date nu poate fi selectată, do_connec afişează un mesaj de eroare, închide conexiunea si
returnează NULL pent indica eşecul operaţiei.
• Exemplul 2. Acest exemplu se bazează pe modificările aduse funcţiei do_connect( j primul exemplu. Aceste
modificări au ca rezultat trei seturi de apeluri la funcţiilfl eroare mysql_errno() şi mysql_error() şi este chiar
obositor să scrii funcţiile res|| tive de fiecare dată când programul trebuie să „protesteze" la apariţia unei prob| De
asemenea, programul de afişare a erorii este agresiv din punct de vedere vizt dificil de citit. Este mai uşor să citiţi
ceva de genul acesta: print_error(conn, "mysql_real_connect() failed"); Deci, haideţi să încapsulăm scrierea
erorilor într-o funcţie print_error (). Putem : funcţia astfel încât să efectueze o anumită operaţie chiar si în situaţia
când conn este l Astfel, putem folosi print_error() dacă apelul la funcţia mysql_init() eşuează ! avem o
combinaţie de apeluri (unele pentru fprintf () şi altele pentru print_erroC|
Parcă aud pe cineva din spate care obiectează: „Păi nu sunteţi obligat să apelaţi; funcţii de eroare de fiecare dată
când trebuie să raportaţi o eroare, deci intenţiei: faceţi programul dificil de citit, pentru ca exemplul
dumneavoastră cu încapsula arate mai bine. Şi de fapt nici măcar nu veţi scrie tot programul de afişare a erorii
scrieţi o singură dată şi apoi folosiţi copierea şi lipirea dacă mai aveţi nevoie de Acestea sunt observaţii corecte,
la care voi răspunde astfel:
• Chiar dacă folosiţi copierea şi lipirea, aceste operaţii sunt mai simplu i tuat cu secţiuni mai scurte de program.
• Indiferent dacă preferaţi sau nu să invocaţi ambele funcţii de eroare la i raportare a unei erori, scrierea integrală
a programului de raportare a i în „varianta lungă" duce la tentaţia de a folosi scurtături ş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 îmbunătăţeşte consecvenţa programului.
• Dacă vă decideţi vreodată să modificaţi formatul mesajelor dumneavc eroare, este mult mai uşor dacă trebuie
să efectuaţi modificarea într-un! loc, decât în tot programul. Sau, dacă vă decideţi să scrieţi mesajele de < într-un
fişier jurnal în loc de (sau în afară de) a le scrie în stderr, este i piu dacă trebuie să modificaţi numai funcţia
print_error(). Această flfl este mai puţin expusă la erori si, din nou, reduce tentaţia de a face jumătate de treabă şi
de a fi inconsecvent. *1
Capitolul 6 Interfaţa API MySQL pentru C 251
• Dacă folosiţi un utilitar de depanare atunci când vă testaţi programele, inserţia unui punct de întrerupere în
funcţia de raportare a erorilor este o modalitate convenabilă de a determina programul să se întrerupă atunci când
depanatorul detectează o condiţie de eroare.
Iată funcţia noastră print_error( ) de afişare 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)) ;
Funcţia print_error ( ) se află în fişierul common . c, deci vom adăuga un prototip al acesteia în fişierul common .
h:
void
print_error(MYSQL *conn, char "message); Acum, funcţia do_connect ( ) poate fi modificată pentru a folosi
funcţia 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, iniţializează 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 interfeţelor 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 */
Fişierul nostru sursă principal, clients.c, este asemănător cu client2.c, dar auj| eliminate toate liniile de program
de conexiune şi întrerupere a conexiunii, f^ înlocuite cu apeluri la funcţiile container. Deci, fişierul 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 Interfaţa 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 aplicaţiei */
do_disconnect(conn); exit(O);
Client 4 - Obţinerea parametrilor de conexiune la rulare
Acum, când dispunem de un program de conexiune uşor de modificat si „blindat" în cazul apariţiei erorilor,
suntem pregătiţi să aflăm cum putem face lucruri mai inteligente decât 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 codaţi în program. Pentru a modifica oricare dintre aceste valori, trebuie să editaţi fişierul sursă şi să-1
recompilaţi, ceea ce nu este foarte convenabil, mai ales dacă doriţi să puneţi programul la dispoziţia altor
persoane.
O modalitate frecvent folosită de specificare a parametrilor de conexiune la rulare este de a folosi opţiunile din
linia de comandă. Programele din distribuţia sistemului MySQL acceptă parametrii de conexiune într-una din
cele două forme specificate în tabelul 6.1.
Tabelul 6.1 Opţiuni standard din linia de comandă pentru MySQL
Parametru
Numele gazdei Numele de utilizator Parola
Numărul 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 consecvenţa cu programele client MySQL standard, clientul nostru va accepta aceleaşi formate, ceea ce
este simplu de realizat, deoarece biblioteca client include o 'Vineţie pentru analiza opţiunilor.
h plus, clientul nostru va putea să extragă informaţiile din fişierele cu opţiuni. Aceasta yă permite să plasaţi
parametrii de conexiune în fişierul - /. my. cnf (adică fişierul . my. cnf din catalogul dumneavoastră de bază),
astfel încât să nu fie necesară specificarea lor în de comandă. Biblioteca client facilitează căutarea fişierelor cu
opţiuni MySQL şi extragerea din acestea a tuturor valorilor relevante. Prin adăugarea a numai câteva linii
254 Partea a ll-a Utilizarea interfeţelor de programare ale sistemului MySQL
în programul dumneavoastră, puteţi determina programul să recunoască fişiere!^ J opţiuni si nu trebuie să
reinventaţi roata prin scrierea propriilor dumneavoastră p| grame pentru aceasta. Sintaxa fişierelor cu opţiuni este
descrisă în Anexa E, „Refer* de programe MySQL".
Accesul la conţinutul fişierelor cu opţiuni
Pentru a citi fişierele cu opţiuni în căutarea valorilor parametrilor de conexiune, folc funcţia load-def aults().
Această funcţie caută fişierele cu opţiuni, le analizează cont tul pentru a descoperi orice grupuri de opţiuni care
vă interesează si rescrie vector argumente al programului dumneavoastră (tabloul argv[ ]) pentru a insera infor
din aceste grupuri sub formă de opţiuni ale liniei de comandă la începutul table argv [ ]. Astfel, opţiunile apar ca
şi cum ar fi fost specificate în linia de comandă. Ca; când analizaţi opţiunile comenzii, obţineţi parametrii de
conexiune ca parte a cif normal de analiză a opţiunilor. Opţiunile sunt adăugate la începutul tabloului argv(ţ nu la
sfârşit, astfel încât, dacă parametrii de conexiune sunt într-adevăr specificaţi îrn de comandă, aceştia să apară mai
târziu (si implicit să redefinească) orice opţiuni adăt de funcţia load_def aults ().
J"
Iată un mic program, show_argv, care prezintă modul de utilizare a funcţiei 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, argvţi]);
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 Interfaţa API MySQL pentru C 255
Programul de prelucrare a fişierului cu opţiuni implică următoarele:
• groups [] este un tablou şir de caractere care indică grupurile din fişierele cu opţiuni care vă interesează. Pentru
programele client, specificaţi întotdeauna cel puţin menţiunea "client" (pentru grupul [client]). Ultimul element
al tabloului trebuie să fie NULL.
• my_init () este o rutină de iniţializare care execută unele operaţii de pornire impuse de funcţia load_defaults().
• Funcţia load_def aults () preia patru argumente: prefixul fişierelor dumneavoastră cu opţiuni (acesta trebuie să
fie întotdeauna "my"), tabloul care menţionează grupurile de opţiuni care vă interesează, respectiv adresa
numărului de argumente şi a vectorului de argumente ale programului dumneavoastră. Nu transferaţi valorile
numărului de argumente şi ale vectorului; transmiteţi în schimb adresele lor, deoarece funcţia load_def aults ()
trebuie să le modifice valorile. Reţineţi, mai ales, că deşi argv este un pointer, trebuie să transferaţi &argv, adresa
pointerului respectiv.
Funcţia show_argv îşi afişează argumentele de două ori: prima dată aşa cum le-aţi specificat în linia de comandă,
apoi în urma modificărilor efectuate de load_defaults(). Pentru a vedea efectele funcţiei load_def aults (),
asiguraţi-vă că aveţi un fişier .my. cnf în catalogul dumneavoastră de bază, cu unii parametri specificaţi pentru
grupul [client]. Să presupunem că fişierul .my .cnf se prezintă astfel:
[client]
user=paul
password=secret
host=o_gazda în această situaţie, prin executarea programului show_argv se obţine următorul 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 ieşire ale programului show_argv să vedeţi unele opţiuni care nu se găseau nici în
linia de comandă, nici în fişierul dumneavoastră -/ .my .cnf. în acest caz, opţiunile respective au fost probabil
specificate într-un fişier cu opţiuni la nivel de sistem. De fapt, funcţia load_def aults () caută fişierele /etc /my
.cnf si my .cnf din catalogul de date MySQL înainte de a citi fişierul .my.cnf din catalogul dumnea-I voastră de
bază. (în Windows, funcţia load_defaults() caută fişierele C:\my.cnf, l c: \mysql\data\my. cnf, respectiv fişierul
my. ini din catalogul \Windows\System).
256 Partea a ll-a Utilizarea interfeţelor de programare ale sistemului MySQL
Programele client care folosesc funcţia load_def aults () specifică aproape întc "client" în lista cu grupuri d«
opţiuni (pentru a putea obţine tpate valorile client^ rale din fişierele cu opţiuni), dar puteţi cere şi valori care sunt
specifice propriulvuj neavoastră program. Pur şi simplu înlocuiţi instrucţiunea
char *groups[] = { "client", NULL }; cu următoarea linie de
program: , ;||
char *groups.[J = { "show^arov", "client", NULL }; Apoi, puteţi adăuga un grup [show_argv] la fişierul
dumneavoastră -/ .my.cnf: sj.
[client]
user=paul ?
password=secret
host=o_gazda
[show_argvj
host«alta_gazda ;: -
în urma acestor modificări, 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 opţiunilor în tabloul cu argumente este deterrî ordinea în care acestea sunt
menţionate în fişierul dumneavoastră cu opţk ordinea în care grupurile cu opţiuni sunt menţionate în tabloul
groups [J. , înseamnă că probabil veţi dori să specificaţi grupuri specifice programelor [client] din fişierul
dumneavoastră cu opţiuni. Astfel, dacă specificaţi 6 o| ambele grupuri, valoarea specifică programului va avea
prioritate. Puteţi vedea; în exemplul prezentat anterior: opţiunea host a fost specificata atât îrl grupul'^ cât si în
grupul [ show_argv ] dar, deoarece grupul [ show_argv] apare ultimul în \ opţiuni, valoarea sa host apare mai
târziu în vectorul cu argumente şi are pric
Funcţia load_def aults () nu selectează valori din parametrii dumneavoastră^ Dacă donţi să folosiţi valorile unor
variabile de mediu precum MYSQL_TCP MYSQL_UNIX_PORT, trebuie să vă ocupaţi personal de acest lucru
prin intermediul l getenv(). Nu voi adăuga această funcţionalitate la clienţii noştri, dar iată un i verificare a
valorilor a două dintre variabilele de mediu standard legate de M;
Capitolul 6 Interfaţa 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 » QetewŢMYSQLJJNIXJ'ORŢ'!) 1= NU14-)
socketjiame = p; , , ;. -.;
în cazul clienţilor MySQL standard, valorile variabilelor de mediu au o prioritate mai redusă decât valorile
specificate în fişierele cu opţiuni sau în linia de comandă. Dacă verificaţi variabilele membru şi doriri să
respectaţi convenţia respectivă, verificaţi variabilele de mediu înainte, nu după apelarea funcţiei load_def aults ()
sau prelucrarea opţiunilor din linia de comandă.
- 't
Analiza argumentelor din linia de comandă
în acest moment, putem prelua toţi parametrii de conexiune în vectorul cu argumente, dar avem nevoie de o
modalitate de analiză a vectorului. Funcţia getoptO*are exact această destinaţie.
Funcţia getopt_long() este încorporată în biblioteca client MySQL, deci puteţi avea acces la aceasta ori de câte
ori stabiliţi legături cu funcţii din biblioteca respectivă, în fişierul dumneavoastră sursă, trebuie si includeţi
fişierul antet getopt. h. Puteţi copia acest fişier antet din catalogul include al distribuţiei sursă MySQL în
catalogul în care vă dezvoltaţi programul client. !< ' "r -' ;;
•i . . 't. C -.-"•: ' -•
Funcţia load_def aults (•) şi sewritoteo
Poate vă puneţi întrebări cu privire la implicaţiile legate de .spionarea" proceselor pe care le poate avea
solicitarea ca funcţia load_def aults () să insereze textul parolelor în lista dumneavoastră cu argumente, deoarece
programe precum ps pot afişa listele cu argumente pentru procese arbitrare. Nu este nici o problemă, deoarece ps
afişează conţinutul original al tabloului argv[ ]. Toate argumentele de tip parolă create de funcţia load_def aults
() indică spre o regjune pe care funcţia 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ă îngrijiţi
să o ştergeţi. Secţiunea .Analiza argumentelor din linia de comandă" vă arată cum să procedaţi.
Programul următor, show_param, foloseşte funcţia load_defaults() pentru citirea fişierelor cu opţiuni, apoi
apelează-funcţia getopt_long<) pentru analiza vectorului cu argumente. show_param arată ce se întâmplă în
fiecare faza a prelucrării argumentelor, Prin efectuarea următoarelor acţiuni: !• Configurează valorile prestabilite
pentru numele gazdei, numele de utilizator şi parola.
2- Afişează valorile originale ale parametrilor de conexiune şi valorile din vectorul cu
argumente. ' ; u'' ••
258 Partea a ll-a Utilizarea interfeţelor de programare ale sistemului MySQL
3. Apelează funcţia load_defaults() pentru a rescrie vectorul cu argumente astfdij acesta să reflecte conţinutul
fişierului cu opţiuni, după care afişează vectorul re
4. Apelează funcţia getopt_long () pentru prelucrarea vectorului cu argumente, ap sează valorile rezultante ale
parametrilor şi ceea ce a mai rămas în vectorul cu :
show_param vă permite să exersaţi diferite modalităţi de specificare a parametrik conexiune (situaţi în fişierele
cu opţiuni sau în linia de comandă) şi să vedeţi rezult prin afişarea valorilor care vor fi folosite pentru stabilirea
unei conexiuni. show_ este util pentru a vă face o idee privind ceea ce se va întâmpla în următorul nostru^ gram
client, atunci când corelăm acest program de prelucrare a parametrilor cu noastră de conectare do_connect ().
Iată cum se prezintă fişierul 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 Interfaţa 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 interfeţelor 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 foloseşte funcţia getopt_long (), care o apelaţi, în mod
caracteristic, într-un ciclu: 3
while ((c = getopt_long(argc, argv, "h: p: :u:P:S", long_options, &option_index)) != EOF)
{
/* opţiune de proces */
}
Primele două argumente ale funcţiei getopt_long() le constituie numărul de argur al programului dumneavoastră
şi vectorul cu argumente. Al treilea argument îl re zintă literele de opţiuni care doriţi să fie recunoscute. Acestea
sunt formele cu mj scurt ale opţiunilor programului dumneavoastră. Literele de opţiune pot fi urmat două puncte,
de două puncte dublate (::) sau de nici un asemenea caracter, pent arăta că opţiunea trebuie să fie urmată, poate fi
urmată, respectiv nu este urmată valoare a opţiunii. Cel de-al patrulea argument, long_options, este un pointer
spr tablou cu structuri de opţiune, fiecare din aceste structuri specificând informaţii ] o opţiune pe care doriţi ca
programul dumneavoastră să o înţeleagă. Rolul său este st| Iar cu acela al şirului de opţiuni din al treilea
argument. Cele patru elemente ale fiec structuri long_opt ions [] sunt următoarele:
• Numele lung al opţiunii.
• O valoare a opţiunii. Valoarea poate fi required_argument (argument obligate optional_argument (argument
facultativ) sau no_argument (fără argument), indică dacă opţiunea trebuie urmată, poate fi urmată, respectiv nu
este urmată valoare a opţiunii. (Aceste valori au acelaşi rol ca si caracterul două puncte, cară două puncte dublate
si respectiv absenţa oricărui caracter din al treilea argument, < conţine şirul de opţiuni.)
• Un argument indicator (flag). Puteţi folosi acest argument pentru a stoca un j
la o variabilă. Dacă se găseşte opţiunea, getopt_long () stochează în variabilă valet specificată de al patrulea
argument. Dacă indicatorul este NULL, getopt_long () coK rează variabila optarg astfel încât să indice spre orice
valoare care urmează după of şi returnează numele scurt al opţiunii, în cazul nostru, tabloul long_options[ ] sg
fică NULL pentru toate opţiunile. Astfel, getopt_long () returnează fiecare argumefl cum îl întâlneşte astfel încât
să poată fi prelucrat în instrucţiunea switch.
Capitolul 6 Interfaţa API MySQL pentru C 261
• Numele scurt (dintr-un singur caracter) al opţiunii. Numele scurte precizate în tabloul long_options[ ] trebuie să
corespundă literelor folosite în şirul cu opţiuni pe care îl transferaţi drept al treilea argument al funcţiei
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 cărei elemente sunt toate egale cu 0.
Cel de-al cincilea argument al funcţiei getopt_long() este un pointer la o variabilă de tip int. getopt_long ()
stochează în această variabilă indexul structurii long_options [ ] care corespunde ultimei opţiuni întâlnite.
(show_param nu face nimic cu această valoare.)
Reţineţi că opţiunea pentru parolă (specificată sub forma - -password sau -p) poate lua o valoare opţională. Cu
alte cuvinte, o puteţi specifica sub forma —password sau --pass-word=parola_dumneavoastră dacă folosiţi forma
de opţiune lungă, respectiv -p sau
- -pparola_dumneavoastra dacă folosiţi forma de opţiune scurtă. Natura opţională a valorii parolei este indicat!
de caracterul:: plasat după litera p din şirul cu opţiuni, precum şi de specificaţia optional_argument din tabloul
long_options[]. De regulă, clienţii MySQL vă permit să omiteţi valoarea parolei în linia de comandă, după care
vă solicită această valoare. Acest procedeu vă permite să evitaţi furnizarea parolei în linia de comandă, ceea ce
împiedică pe alţii să vă citească parola prin „spionarea" procesului. Când vom scrie următorul client, client4,
vom adăuga la acesta logica de verificare a parolei.
Iată un exemplu de invocare a programului show_param si a datelor de ieşire rezultate (presupunând că -/.my.cnf
are acelaşi conţinut 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 interfeţelor 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 ieşire arată că numele gazdei este selectat din linia de comandă (redc valoarea din fişierul cu opţiuni),
precum si că numele de utilizator si parola provin ld fişierul cu opţiuni. getopt_long() analizează corect opţiunile,
indiferent dacă ac sunt specificate în forma de opţiune scurtă (-h nume_gazda) sau în forma de op lungă (--
user=paul, --password=secret).
Acum, să eliminăm liniile de program care nu au decât rolul de a ilustra modii funcţionare a rutinelor de tratare a
opţiunilor şi să folosim restul programului cai pentru un client care se conectează la un server în conformitate cu
opţiunile cari i furnizate într-un fişier cu opţiuni sau în linia de comandă. Fişierul 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 Interfaţa 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 interfeţelor 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 aplicaţiei propriu -zise */
do_disconnect(conn) ; exit(O); }
în comparaţie cu programele clienţi, client2 şi clients pe care le-am creat ane client4 efectuează câteva operaţii
pe care nu le-am întâlnit până acum:
• Permite specificarea numelui bazei de date în linia de comandă, după opţiunii* i sunt analizate de getopt_long (
) . Această comportare este similară cu aceea a clie standard din distribuţia MySQL.
O
1.
2.
L
schiţă generală a algoritmului de afişare este următoarea:
Se determină lăţimea de afişare pentru fiecare coloană.
Se afişează un rând de etichete de coloană „încasetate" (delimitate prin bare verticale
şi precedate, respectiv urmate de rânduri formate din liniuţe).
Se afişează valorile din fiecare rând al setului de rezultate, cu fiecare coloană încase-tată (delimitată prin bare
verticale) şi aliniată pe verticală, în plus, numerele sunt afişate aliniate la dreapta şi se afişează cuvântul "NULL"
în locul valorilor NULL.
280 Partea a ll-a Utilizarea interfeţelor de programare ale sistemului MySQL
4. La sfârşit, se afişează un număr al rândurilor regăsite. Acest exerciţiu reprezintă o bună demonstraţie a
utilizării metadatelor setului de n ţaţe. Pentru a afişa rezultatele aşa cum s-a arătat mai sus, trebuie să ştim mai
lucruri despre setul de rezultate, nu numai valorile datelor incluse în rânduri.
Poate vă gândiţi: „Hmm, descrierea aia arată suspect de asemănător cu modul în mysql îşi afişează rezultatele".
Da, aşa este, iar dumneavoastră sunteţi invitat să paraţi codul sursă al programului mysql cu programul
final al fum process_result_set() revizuite. Cele două nu sunt identice, dar comparaţia două abordări ale
aceleiaşi probleme este instructivă.
Mai întâi, trebuie să determinăm lăţimea de afişare a fiecărei coloane. Listingul vă arată cum să procedaţi.
Calculele se bazează în întregime pe metadatele setuli rezultate şi nu se face nici o referire la valorile din rânduri:
MYSQL_FIELD *field;
unsigned int i, col, len;
/* determina lăţimile de afişare 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 cuvântului "NULL" */ field->max_length = col_len; /* reinitializarea
informaţiilor
despre coloana */
Lăţimile coloanelor sunt calculate prin parcurgerea ciclică a structurilor MYSQL_FI! pentru coloanele din setul
de rezultate. Ne poziţionăm pe prima structură prin apela funcţiei mysql_fetch_seek(). Apelurile ulterioare la
funcţia mysql_fetch_fiel| returnează pointer! spre structurile coloanelor succesive. Lăţimea unei coloane afişare
o reprezintă cea mai mare din trei valori, fiecare depinzând de metadat&e structura cu informaţii privind
coloanele:
• Lungimea câmpului 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 conţine valori NULL, arată dacă acea coloană poate
conţine NULL sau nu.
Reţineţi că, după ce lăţimea de afişare pentru coloană a devenit cunoscută, at această valoare variabilei
max_length, care este un membru al unei structuri pe obţinem din biblioteca client. Acest lucru este permis sau
este necesar ca dateţiey 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 distribuţia MyS
£&',*.' " '• '
Capitolul 6 Interfaţa AP) MySQL pentru C 281
modifică valoarea max_length într-un mod asemănător, deci voi presupune că procedeul este permis. (Dacă
preferaţi o abordare alternativă, care nu modifică max_length, alocaţi un tablou de valori unsigned int şi stocaţi
lăţimile calculate în tabloul respectiv.)
Calculul lăţimilor de afişare ascunde o capcană. Vă mai reamintiţi că max_length nu are nici o semnificaţie
atunci când creaţi un set de rezultate folosind mysql_use_result(). Deoarece avem nevoie de max_length pentru a
determina lăţimea de afişare a valorilor din coloană, funcţionarea corectă a algoritmului impune ca setul de
rezultate să fie generat folosind mysql_store_result( )2.
Odată cunoscute lăţimile coloanelor, suntem gata de afişare. Titlurile sunt uşor de manipulat; pentru o coloană
dată, folosim pur si simplu structura cu informaţii despre coloană indicată prin field şi afişăm membrul name,
folosind lăţimea calculată anterior:
printf (" %-*s |", field->max_length, field->name);
Pentru date, vom parcurge ciclic rândurile din setul de rezultate, afişând valorile coloanelor pentru rândul curent
în timpul fiecărei iteraţii. Afişarea valorilor din coloanele unui rând este oarecum ciudată, deoarece o valoare
poate fi NULL sau poate reprezenta un număr (caz în care îl afişăm aliniat la dreapta). Valorile coloanelor sunt
afişate după cum urmează, unde row [ii conţine valorile datelor, iar field indică spre informaţiile 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 macroinstrucţiunii IS_NUM() este adevărată dacă tipul coloanei indicat de field ->type este un tip
numeric, precum INT, FLOAT sau DECIMAL.
Liniile de program finale pentru afişarea setului de rezultate se prezintă astfel. Reţineţi că, deoarece afişăm de
mai multe ori linii compuse din mici liniuţe, codul pentru această operaţie este încapsulat în propria sa funcţie,
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 soluţie utilă dacă folosiţi mysql_use_result() în locul funcţiei mysql_store_result().-
N.A.
282 Partea a ll-a Utilizarea interfeţelor 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 lăţimile de afişare 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 cuvântului "NULL" */ field->max_length = col_len; /* reinitializarea
informaţiilor
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 Interfaţa 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 rânduri returnate\n", (unsigned long)
mysql_num_rows (res_set)); }
Biblioteca client MySQL furnizează numeroase modalităţi de acces la structurile cu informaţii despre coloane.
De exemplu, programul din exemplul anterior obţine de mai multe ori accesul la aceste structuri, folosind cicluri
având următoarea 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, combinaţia dintre funcţiile mysql_f ield_seek () si mysql_f etch_f ield () este numai una din
modalităţile de a obţine structurile MYSQL_FIELD. Pentru a afla despre alte modalităţi de obţinere a structurilor
cu informaţii despre coloane, vezi informaţiile aferente funcţiilor mysql_fetch_f ields() şi mysql_f etch_f
ield_direct() din Anexa F.
Client 5 - Un program interactiv de interogare
Să adunăm o buna parte din programele create până acum si să le folosim pentru a scrie un program client
interactiv simplu. Acesta vă permite să introduceţi interogări, le execută folosind rutina noastră de uz general
pentru prelucrarea interogărilor process_query() şi afişează rezultatele folosind programul de formatare a datelor
de ieşire process_result_set (), creat în secţiunea precedentă.
clients va fi similar din multe puncte de vedere cu mysql, deşi nu va avea, desigur, un număr atât de mare de
caracteristici. Există numeroase restricţii cu privire la datele de intrare care vor fi acceptate de clients:
* Fiecare linie de intrare trebuie să conţină o singură interogare completă.
* Interogările nu trebuie să se încheie cu punct şi virgulă sau cu \g.
* Comenzi precum quit nu sunt recunoscute; în schimb, folosiţi Control-D pentru a termina execuţia
programului.
284 Partea a ll-a Utilizarea interfeţelor 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 adăugat este un ciclu care adună datele de intrare si le execută. Pentru a
construi programul clients, începeţi prin a copia scheletul de program < client4.c în programul clients.c. Apoi
adăugaţi la acesta liniile de program afere funcţiilor process_query(), process_result_set() şi print_dashes(). în
final, în p*i gramul clients. c, căutaţi linia din funcţia main () care are următorul conţinut:
/* aici se inserează codul aplicaţiei propriu-zise */ Apoi înlocuiţi-o cu următorul ciclu while:
while (1)
char buf[1024];
fprintf (stderr, "query> "); if (fgets (buf, sizeof (buf);
break; process_query (conn, buf);
/* prompt de afişare */ stdin) = = NULL) /* citeşte interogarea */
/* executa interogarea */
Compilaţi programul clients.c pentru a produce fişierul clients.o, legaţi clienţii cu common.o şi cu biblioteca
client pentru a genera fişierul clients şi aţi terminat! Aw un program client MySQL interactiv, care poate executa
orice interogare si afiş rezultatele.
Alte aspecte
Această secţiune tratează numeroase subiecte care nu s-au putut încadra foarte bit evoluţia de la clienţi 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 interogări.
• Modul de lucru cu datele de tip imagine.
• Modul de obţinere a informaţiilor referitoare la structura tabelelor dumneavoasţ
• Greşeli comune de proiectare în MySQL şi modul de evitare a acestora.
Efectuarea de calcule cu seturile de rezultate
Până acum, ne-am concentrat asupra utilizării metadatelor aferente seturilor de rezul mai ales pentru afişarea
datelor din rânduri, dar este evident că vor exista situaţii " datele dumneavoastră vor trebui folosite şi în alte
moduri, nu numai afişate. De exti puteţi efectua calcule statistice în funcţie de valorile datelor, folosind
metadatele vă asigura că datele se conformează cerinţelor pe care doriţi ca acestea să le satisfa
imagine_id int(11)
imagine_data blob YES PRI i 0 i NULL
Dacă executaţi aceeaşi interogare din propriul dumneavoastră client, veţi primi aceleaşi «iformaţii (fără casete).
290 Partea a ll-a Utilizarea interfeţelor de programare ale sistemului MySQL
Dacă doriţi informaţii numai despre o singură coloană, folosiţi această interogare:
SHOW FIELDS FROM nume_tabel LIKE "nume_coloana" Interogarea va returna aceleaşi coloane, dar un
singur rând (sau nici un rând, în cazul rJ care coloana nu există).
Greşeli de programare a programelor client care trebuie evitate
Această secţiune discută despre unele erori comune de programare a interfeţelor . 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.)
Greşeala 1: Utilizarea unor pointer! neiniţializaţi ai variabilelor de tratare a conexiunilor
în exemplele prezentate în acest capitol, am apelat funcţia mysql_init() transferând acesteia un argument NULL.
Astfel, se indică funcţiei 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
iniţializa; structură şi va returna un pointer către aceasta fără a aloca structura însăşi. Dacă doriţi i folosiţi această
a doua metodă, aceasta poate duce la anumite probleme dificil de depis Următoarea expunere va scoate în
evidenţă unele probleme de evitat.
Dacă transferaţi un pointer funcţiei mysql_init (), acesta trebuie să indice spre ceva...'{ luăm această componentă
de program: main()
MYSQL *conn;
mysql_init (conn);
Problema este că mysql_init () primeşte un pointer, dar pointerul nu indică spre : semnificativ, conn este o
variabilă locală şi, implicit, constituie un spaţiu de st neiniţializat, care poate indica oriunde atunci când funcţia
main () îşi începe execuţii Aceasta înseamnă că mysql_init () va folosi pointerul si va scrie undeva într-o zonă
aleatoare de memorie. Dacă aveţi noroc, conn va indica undeva în afara spaţiului de adrese al programului
dumneavoastră şi sistemul va termina imediat execuţia prograft. mului, deci veţi sesiza apariţia problemei încă
de la primele linii de program. Dacă a»' sunteţi atât de norocos, conn va indica spre date pe care le folosiţi mai
târziu în program^* iar dumneavoastră nu veţi sesiza apariţia unei probleme decât atunci când programaşi
încearcă efectiv să folosească datele respective, în acest caz, problema va părea că se pvoHt' duce într-o fază a
programului mult mai avansată decât aceea din care provine efectiv şi poate fi mult mai dificil de depistat. Iată
un program „cu probleme" asemănător: MYSQL *conn;
main()
Capitolul 6 Interfaţa 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 iniţializată la O (adică NULL) înainte de pornirea
programului. mysql_init () vede un argument NULL, deci se iniţializează şi alocă o nouă variabilă de tratare a
conexiunii. Din păcate, conn este încă NULL, deoarece niciodată nu îi este atribuită vreo valoare. De îndată ce
transferaţi conn unei funcţii din interfaţa API în C pentru MySQL care impune o variabilă de tratare a conexiunii
diferită de NULL, programul dumneavoastră va cădea. Pentru ambele programe, remediul îl constituie
asigurarea faptului că variabila conn are o anumită valoare. De exemplu, o puteţi iniţializa la adresa unei
structuri MYSQL deja alocate: MYSQL conn_struct, *conn = &conn_struct;
mysql_init (conn);
Totuşi, soluţia recomandată (şi mai uşoară!) este pur şi simplu de a transfera NULL în mod explicit funcţiei
mysql_init(), de a permite acelei funcţii să aloce automat structura MYSQL şi să atribuiţi variabilei conn
valoarea returnată:
MYSQL *conn;
conn = mysql_init (NULL);
în orice caz, nu uitaţi să testaţi valoarea returnată a funcţiei mysql_init(), pentru a vă asigura că nu este NULL.
Greşeala 2: Lipsa testului validităţii unui set de rezultate
Nu uitaţi să verificaţi starea apelurilor de unde vă aşteptaţi să obţineţi 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 păcate, dacă mysql_store_result() eşuează, res_set este NULL si ciclul while nici măcar nu mai trebuie
executat. Testaţi valoarea returnată de funcţiile care returnează seturi de rezultate, pentru a vă asigura că aveţi
efectiv un „material de lucru".
Greşeala 3: Ignorarea valorilor NULL din coloane
Nu uitaţi să verificaţi dacă valorile coloanelor din tabloul MYSQL_ROW returnat de către funcţia
mysql_fetch_row() sunt pointeri NULL. Pe unele calculatoare, programul următor „cade" dacă row[i] este
NULL: