Oracle et le C 2eme Partie : Oracle PRO*C

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, &amp;buf_len, &amp;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).

Publicité

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s