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).