On a déjà installer coté client toutes les librairies qu’il faut pour Oracle et notamment le précompilateur proc (le package oracle-instantclient11.2-precomp-11.2.0.3.0-1.i386.rpm), assurez vous que vous l’avez bien :
toc@tocNewServer:~$ which proc /usr/bin/proc
Ce binaire va nous permettre de compiler fichier contenant des requêtes SQL embarquées dans du code C (Embedded SQL), ce type de fichier d’appelle le PRO*C et a pour extension .pc.
Le but du binaire proc est de transformer les fichiers .pc en des fichiers C classiques, il va parser le fichier .pc et remplacer toutes les instructions qui contiennent EXEC SQL par le code C qui va bien. Pour plus d’infos.
On va créer un fichier PROC*C qui va se connecter a la base et faire un SELECT et afficher le résultat de la requête. Voici le contenu du fichier hello-oracle-proc.pc :
/* * hello-oracle-proc.pc : * Un example de fichier PRO*C qui demontre comment faire un simple SELECT * dans une base Oracle */ #include <stdio.h> #include <string.h> #include <stdlib.h> /* Les includes du PRO*C */ #include <sqlca.h> #include <sqlcpr.h> /* Les identifiants pour se connecter a Oracle */ #define DB_USER "hr" #define DB_PASSWORD "human" #define DB_NAME "TOCORACL" /* Definit dans tnsnames.ora */ #define NAME_LEN 30 #define ERR_LEN 512 #define FIRST_NAME_LEN 20 #define LAST_NAME_LEN 25 /* * En cas d'erreur cette fonction sera appelé * pour nous donner plus d'infos */ void sql_error(char *msg) { char err_msg[ERR_LEN]; size_t buf_len, msg_len; EXEC SQL WHENEVER SQLERROR CONTINUE; printf("\n"); if (msg) { printf("%s\n", msg); } buf_len = sizeof (err_msg); /* sqlglm est une fonction Oracle qui permet * d'avoir plus d'informations sur l'erreur */ sqlglm(err_msg, &buf_len, &msg_len); printf("%.*s", msg_len, err_msg); EXEC SQL ROLLBACK RELEASE; exit(EXIT_FAILURE); } int main(int argc, char **argv) { /* Ici on commence la section de declaration des variables "SQL" */ EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[NAME_LEN]; /* User qui va se connecter a la base */ VARCHAR password[NAME_LEN]; /* Mot de passe du user */ VARCHAR database[NAME_LEN]; /* Nom de l'instance a la quelle on veut se connecter */ /* * Ici les "host variables" qui permettent de stocker les resultats * des requetes */ /* Les variables qu'on veut recuperer * faites : "describe employees;" dans sqlplus * pour recuperer la tailles des colonnes qu'on * veut recuperer */ int emp_dept; VARCHAR emp_first_name[FIRST_NAME_LEN + 1]; /* Le "1" pour '\0' */ VARCHAR emp_last_name[LAST_NAME_LEN + 1]; int emp_salary; /* * Ici les "indicator variables" qu'on utilise pour savoir si on * a recuperé une valeur NULL */ short emp_dept_ind; short emp_first_name_ind; short emp_last_name_ind; short emp_salary_ind; EXEC SQL END DECLARE SECTION; /* Fin de la section de declaration des variables SQL */ /* * Les valeurs qu'on passe dans les requetes sont de type * VARCHAR ==> On fait la conversion depuis le type "char *" * vers VARCHAR */ strncpy((char *) username.arr, DB_USER, NAME_LEN); username.len = (unsigned short) strlen((char *) username.arr); strncpy((char *) password.arr, DB_PASSWORD, NAME_LEN); password.len = (unsigned short) strlen((char *) password.arr); strncpy((char *) database.arr, DB_NAME, NAME_LEN); database.len = (unsigned short) strlen((char *) database.arr); /* * Si une erreur se produit on veut en etre informé * via le fonction sql_error qu'on va definir plus tard */ EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error: \n"); /* Connexion a la base */ EXEC SQL CONNECT :username IDENTIFIED BY :password USING :database; /* Quand y a plus de resultats, on fait un break */ EXEC SQL WHENEVER NOT FOUND DO BREAK; /* * On declare un curseur (peut etre vu comme une sorte de buffer * qui va contenir le résultat de notre requete et qui nous permet * de traverser toutes les lignes "ROW" du résultat) */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT department_id, first_name, last_name, salary FROM employees WHERE PHONE_NUMBER is not null; EXEC SQL OPEN emp_cursor; /* * On execute la requete et on parcourt * le resultat */ for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_dept:emp_dept_ind, :emp_first_name:emp_first_name_ind, :emp_last_name:emp_last_name_ind, :emp_salary:emp_salary_ind; /* On regarde si l'une des "indicator variables" a un probleme */ /* Si une "indicator variable" contient : * 0 ==> tout s'est bien passé * -1 ==> la variable est NULL */ /* dans la description de la tables employees, la colonne last_name ne peut etre NULL */ emp_last_name.arr[emp_last_name.len] = '\0'; if (emp_dept_ind == -1) { printf("Oups on a un department_id NULL pour l'employé %s!\n", emp_last_name.arr); emp_dept = 0; } if (emp_first_name_ind == -1) { printf("Oups pas de prenom!, on va l'appeler toto :-\n"); strncpy(emp_first_name.arr, "toto", FIRST_NAME_LEN); } if (emp_salary_ind == -1) { printf("Oups un benevole :-\n"); emp_salary = 0.0; } emp_first_name.arr[emp_first_name.len] = '\0'; printf("<%d>, <%s>, <%s>, <%d>\n", emp_dept, emp_first_name.arr, emp_last_name.arr, emp_salary); } /* * Fermer le curseur et la connexion */ EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT RELEASE; return EXIT_SUCCESS; }
Voici quelques remarques pour comprendre le code :
– Les requêtes SQL dans du PRO*C commencent toutes par : EXEC SQL suivi de la requête normale que vous saisissez dans un outil tel que sqlplus ou autre.
– Les « indicator variables » ne sont pas obligatoire, on peut bien faire ceci :
EXEC SQL FETCH emp_cursor INTO :emp_dept, :emp_first_name, :emp_last_name, :emp_salary;
et supprimer la partie concernant les « indicator variables » dans la suite du code
– Pour passer une variable en tant que paramètre dans une requête, on fait : « :variable«
– La structure d’une variable VARCHAR est de la forme :
struct { unsigned short len; unsigned char arr[len]; } my_varchar_variable;
donc pour passer de VARCHAR a (char *), il suffit de faire :
strncpy(my_char_variable, my_varchar_variable.arr, len);
Et pour passer de (char *) a VARCHAR, on fait :
strncpy(my_varchar_variable.arr, my_char_variable, strlen(my_char_variable)); my_varchar_variable.len = strlen(my_char_variable)
Puis on compile avec :
proc hello-oracle-proc.pc
Si vous avez des erreurs lors de cette étape modifiez alors le fichier /usr/lib/oracle/11.2/client/precomp/admin/pcscfg.cfg (ou créer le s’il n’existe pas) en mettant les lignes suivantes (Attention les paths des includes peuvent changer d’une machine a une autre et d’un système a un autre, si vous avez des problèmes n’hésitez pas) :
sys_include=(/usr/lib/gcc/i686-linux-gnu/4.6/include,/usr/include,/usr/include/oracle/11.2/client,/usr/include/i386-linux-gnu) ltype=short
cette première étape va générer un fichier C : hello-oracle-proc.c, qu’on compilera en suite de façon classique :
gcc -Wall hello-oracle-proc.c -I/usr/include/oracle/11.2/client -o hello-oracle-proc -lclntsh
Puis on exécute le binaire généré et on aura un résultat comme celui ci :
toc@tocNewServer:~/toc_src/DATABASES/Oracle$ ./hello-oracle-proc <50>, <Donald>, <OConnell>, <2600> <50>, <Douglas>, <Grant>, <2600> <10>, <Jennifer>, <Whalen>, <4400> <20>, <Michael>, <Hartstein>, <13000> <20>, <Pat>, <Fay>, <6000> <40>, <Susan>, <Mavris>, <6500> <70>, <Hermann>, <Baer>, <10000> <110>, <Shelley>, <Higgins>, <12008> <110>, <William>, <Gietz>, <8300> <90>, <Steven>, <King>, <24000> <90>, <Neena>, <Kochhar>, <17000> <90>, <Lex>, <De Haan>, <17000> <60>, <Alexander>, <Hunold>, <9000> <60>, <Bruce>, <Ernst>, <6000> <60>, <David>, <Austin>, <4800> <60>, <Valli>, <Pataballa>, <4800> <60>, <Diana>, <Lorentz>, <4200> <100>, <Nancy>, <Greenberg>, <12008> <100>, <Daniel>, <Faviet>, <9000> <100>, <John>, <Chen>, <8200> <100>, <Ismael>, <Sciarra>, <7700> <100>, <Jose Manuel>, <Urman>, <7800> <100>, <Luis>, <Popp>, <6900> <30>, <Den>, <Raphaely>, <11000> <30>, <Alexander>, <Khoo>, <3100> <30>, <Shelli>, <Baida>, <2900> <30>, <Sigal>, <Tobias>, <2800> <30>, <Guy>, <Himuro>, <2600> <30>, <Karen>, <Colmenares>, <2500> <50>, <Matthew>, <Weiss>, <8000> <50>, <Adam>, <Fripp>, <8200> <50>, <Payam>, <Kaufling>, <7900> <50>, <Shanta>, <Vollman>, <6500> <50>, <Kevin>, <Mourgos>, <5800> <50>, <Julia>, <Nayer>, <3200> <50>, <Irene>, <Mikkilineni>, <2700> <50>, <James>, <Landry>, <2400> <50>, <Steven>, <Markle>, <2200> <50>, <Laura>, <Bissot>, <3300> <50>, <Mozhe>, <Atkinson>, <2800> <50>, <James>, <Marlow>, <2500> <50>, <TJ>, <Olson>, <2100> <50>, <Jason>, <Mallin>, <3300> <50>, <Michael>, <Rogers>, <2900> <50>, <Ki>, <Gee>, <2400> <50>, <Hazel>, <Philtanker>, <2200> <50>, <Renske>, <Ladwig>, <3600> <50>, <Stephen>, <Stiles>, <3200> <50>, <John>, <Seo>, <2700> <50>, <Joshua>, <Patel>, <2500> <50>, <Trenna>, <Rajs>, <3500> <50>, <Curtis>, <Davies>, <3100> <50>, <Randall>, <Matos>, <2600> <50>, <Peter>, <Vargas>, <2500> <80>, <John>, <Russell>, <14000> <80>, <Karen>, <Partners>, <13500> <80>, <Alberto>, <Errazuriz>, <12000> <80>, <Gerald>, <Cambrault>, <11000> <80>, <Eleni>, <Zlotkey>, <10500> <80>, <Peter>, <Tucker>, <10000> <80>, <David>, <Bernstein>, <9500> <80>, <Peter>, <Hall>, <9000> <80>, <Christopher>, <Olsen>, <8000> <80>, <Nanette>, <Cambrault>, <7500> <80>, <Oliver>, <Tuvault>, <7000> <80>, <Janette>, <King>, <10000> <80>, <Patrick>, <Sully>, <9500> <80>, <Allan>, <McEwen>, <9000> <80>, <Lindsey>, <Smith>, <8000> <80>, <Louise>, <Doran>, <7500> <80>, <Sarath>, <Sewall>, <7000> <80>, <Clara>, <Vishney>, <10500> <80>, <Danielle>, <Greene>, <9500> <80>, <Mattea>, <Marvins>, <7200> <80>, <David>, <Lee>, <6800> <80>, <Sundar>, <Ande>, <6400> <80>, <Amit>, <Banda>, <6200> <80>, <Lisa>, <Ozer>, <11500> <80>, <Harrison>, <Bloom>, <10000> <80>, <Tayler>, <Fox>, <9600> <80>, <William>, <Smith>, <7400> <80>, <Elizabeth>, <Bates>, <7300> <80>, <Sundita>, <Kumar>, <6100> <80>, <Ellen>, <Abel>, <11000> <80>, <Alyssa>, <Hutton>, <8800> <80>, <Jonathon>, <Taylor>, <8600> <80>, <Jack>, <Livingston>, <8400> Oups on a un department_id NULL pour l'employé Grant! <0>, <Kimberely>, <Grant>, <7000> <80>, <Charles>, <Johnson>, <6200> <50>, <Winston>, <Taylor>, <3200> <50>, <Jean>, <Fleaur>, <3100> <50>, <Martha>, <Sullivan>, <2500> <50>, <Girard>, <Geoni>, <2800> <50>, <Nandita>, <Sarchand>, <4200> <50>, <Alexis>, <Bull>, <4100> <50>, <Julia>, <Dellinger>, <3400> <50>, <Anthony>, <Cabrio>, <3000> <50>, <Kelly>, <Chung>, <3800> <50>, <Jennifer>, <Dilly>, <3600> <50>, <Timothy>, <Gates>, <2900> <50>, <Randall>, <Perkins>, <2500> <50>, <Sarah>, <Bell>, <4000> <50>, <Britney>, <Everett>, <3900> <50>, <Samuel>, <McCain>, <3200> <50>, <Vance>, <Jones>, <2800> <50>, <Alana>, <Walsh>, <3100> <50>, <Kevin>, <Feeney>, <3000>
La prochaine fois on verra comment interagir avec Oracle en utilisant la librairie oci (Oracle Call Interface).