Développer une application Oracle en C/C++ avec la librairie OCILIBDate de publication : 07/01/2008 , Date de mise à jour : 24/04/2008
Par
Vincent Rogier
Comment programmer simplement et efficacement des applications Oracle performantes en C/C++.
I. Public concerné
II. Présentation
II-A. Révision
II-B. Introduction
II-C. Complexité d'OCI
II-D. Pourquoi OCILIB ?
III. Installation
III-A. Près requis
III-B. Compatibilités
III-C. Installation sous Unixes (Unix/Linux/Mac)
III-D. Installation sous Microsoft Windows
III-E. Configuration de projets
IV. Se Connecter à Oracle
IV-A. Initialiser OCILIB
IV-B. Connexions
IV-C. Gestion des erreurs
IV-D. Transactions
IV-E. Types de donnée supportés
V. Exécuter des ordres SQL
V-A. SQL Statements
V-B. Lier des variables
V-C. Récupérer le résultat d'un select
V-D. PL/SQL blocks
V-E. Support de la clause SQL RETURNING
V-F. Contrôler les statements
VI. Bulk Operations
VI-A. Oracle Array interface
VI-B. Exemples
VI. Utiliser les Larges Objects
VI-A. Internal Objects (LOBs)
VI-B. External Objects (FILEs)
VI-C. Long Objects
VIII. Manipuler les Named Types
VIII-A. Instances autonomes
VIII-B. Sélection d'objets
IX. Manipulation des dates et timestamps
IX-A. Dates
IX-B. Timestamps
IX-C. Intervals
X. Pour aller plus loin
X-A. Support d'Unicode
X-B. Décrire les tables d'un schéma
X-C. Fonctions '"All In One"
X-D. Documentation
XI. Optimiser les performances
XII. Téléchargement
XIII. Fonctionnalités (version 2.4.0)
XIV. Conclusion
XV. Remerciements
I. Public concerné
II. Présentation
II-A. Révision
Liste des révisions du document :
- 07/01/2008 - Publication (version OCILIB 1.5.1)
- 13/02/2008 - Mise à jour (version OCILIB 2.0.0)
- 21/02/2008 - Mise à jour (paragraphe sur les performances + correction de la génération PDF)
- 07/03/2008 - Mise à jour (correction paragraphe III-E - options du linker sous linux/unix)
- 20/03/2008 - Mise à jour (correction paragraphe XIII - Fonctionnalités)
- 29/03/2008 - Mise à jour (correction coquilles code : OCI_Statment -> OCI_Statement + ajouts de commits)
- 01/04/2008 - Mise à jour (version OCILIB 2.3.0 : liste des fonctionnalités)
- 24/04/2008 - Mise à jour (version OCILIB 2.4.0 + diverses modifications du tutoriel)
II-B. Introduction
OCILIB est une librairie Open Source multi plateforme, écrite en C, proposant une gamme d'APIs permettant :
- D'accéder très facilement à des bases de données Oracle.
- D'exécuter des ordres SQL et PL/SQL
- Manipuler les données d'une base de données
II-C. Complexité d'OCI
Oracle Call Interface (OCI) est une API fournie par Oracle permettant aux développeurs de créer des applications en utilisant des appels C/C++ bas niveau afin d'accéder à des bases de données Oracle.
OCI permet de contrôler tous les aspects de l'exécution d'ordres SQL tout en supportant les types de données, les conventions d'appel, la syntaxe et la sémantique des langages C, C++.
OCI est une API très puissante (c'est l'API la plus bas niveau fournie par Oracle) et utilisée par beaucoup d'applications et outils (à commencer par Sql*Plus) mais aussi très complexe et lourde à utiliser...
Par exemple, se connecter à Oracle requiert au minimum quasiment 100 lignes de code ! De plus, la plupart des fonctions d'OCI requiert souvent une liste d'arguments assez conséquente (souvent une dizaine de paramètres, voir plus...)
Au final, une application OCI est souvent pénible à coder, relire et maintenir et les "experts" en OCI ne sont pas aussi nombreux que les codeurs Java, .NET ou VB !
II-D. Pourquoi OCILIB ?
OCILIB encapsule OCI afin de fournir une interface beaucoup plus simple à coder, lire et surtout afin de permettre une réutilisabilité du code optimale.
OCILIB est gratuit (open source - license LGPL) et fournit plus de 350 fonctions simples et son code source est indépendant de toute plateforme.
Les langages C et C++ sont parmi les plus performants et malgré cela, souvent lors du choix d'un langage pour coder une application Oracle, ils ne sont pas retenus, au profit de frameworks de type JAVA et .NET. Ils sont écartés bien souvent pour des raisons d'accès car les API C/C++ pour accéder à Oracle (fournies ou non par Oracle) sont complexes à mettre en oeuvre.
OCILIB permet de concilier les performances de C/C++ avec une mise en oeuvre très simple via une interface la plus pragmatique possible tout en gardant une richesse dans les fonctionnalités.
OCILIB est une des rares librairies C/C++ basées sur OCI apportant un support complet Unicode. Une application OCILIB peut être indifféremment compilée nativement en Ansi ou en Unicode.
Enfin, le code d'OCILIB est 100 % ISO C (C89 et C99 pour l'Unicode) ce qui lui permet d'être extrêmement portable !
OCILIB ne nécessite pas de client Oracle pour développer. En effet, une application utilisant OCILIB peut linker les librairies Oracle à la compilation (liaison statique ou dynamique) ou charger dynamiquement ces librairies en runtime si l'OS cible supporte le chargement dynamique de modules.
OCILIB fonctionne sur toutes les plateformes supportées par Oracle (Microsoft Windows, Linux, Unix, ...).
III. Installation
III-A. Près requis
- Posséder un système Unix like ou Microsoft Windows supporté par Oracle
- Avoir un client Oracle installé (non requis pour la compilation mais pour l'exécution)
III-B. Compatibilités
Les plateformes suivantes ont été validées (compilation, installation, build et exécution d'un projet) :
- Microsoft Windows
- Linux
- Solaris
- HP/UX
- AIX
Les autres plateformes supportées par Oracle (MacOS server, OpenVMS, z/OS) n'ont pas été officellement testées, mais au vu des plateformes déja validées, cela ne devrait pas être trop problématique !
Les compilateurs suivants ont été validés :
- Microsoft Compilers (VC++, VS200X)
- GCC et MinGW
- XLC
- CC propriétaires
Les versions d'Oracle suivantes (clients et serveurs) ont été validées :
- Oracle 8i
- Oracle 9i
- Oracle 10g
- Oracle 11g
III-C. Installation sous Unixes (Unix/Linux/Mac)
OCILIB utilise les outils GNU pour son déploiement et son installation.
- Décompresser l'archive courante (ocilib-x.y.z-gnu.tar.gz)
- $ cd ocilib-x.y.z
- $ ./configure
- $ ./make
- $ ./make install (généralement, il faut passer en root pour l'installation)
Vérifier la variable d'environnement des chemins d'accès des librairies dynamiques (LD_LIBRARY_PATH, LD_PATH, ..) afin que le répertoire des shared libraries d'Oracle s'y trouve ainsi que celui où est installé OCILIB (typiquement /usr/local/lib sous linux)
Typiquement, il suffit de rajouter à son fichier .profile :
> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib:/usr/local/lib
OCILIB supporte 3 options supplémentaires d'installation :
- --with-oracle-import=(linkage|runtime)
- --with-oracle-charset=(ansi|unicode|mixed)
- --with-oracle-home=(oracle directory)
III-D. Installation sous Microsoft Windows
Sous Microsoft Windows, des DLLs sont fournies pour les environnement 32bits et 64bits (x86) et peuvent être recompilées aisément.
- Décompresser l'archive courante (ocilib-x.y.z-windows.zip)
- Copier ocilib\src\ocilib.h dans un répertoire inclus dans la liste de l'option "fichier headers" du compilateur
- Copier ocilib\lib32|64\ocilib[x].lib dans un répertoire inclus dans la liste de l'option "fichier bibliothèques" de l'éditeur de lien
- Copier ocilib\lib32|64\ocilib[x].dll dans un répertoire inclus dans la variable d'environnement PATH
[x] représente la version compilée d'OCILIB : "a" pour Ansi, "w" pour Unicode et "m" pour mixed mode
III-E. Configuration de projets
Pour utiliser OCILIB dans un projet, il faut inclure le header "ocilib.h".
Certaines options doivent être définies avant l'inclusion du header ocilib.h
=> Mode de liaison Oracle :
- OCI_IMPORT_LINKAGE : pour lier les librairies (static or shared) à la compilation (option par défaut sous Unix like)
- OCI_IMPORT_RUNTIME : pour charger dynamiquement les librairies oracle à l'exécution (option par défault sous MS Windows)
=> Charsets utilisés :
- OCI_CHARSET_ANSI : toute les chaînes de caractères sont en Ansi (option par défaut)
- OCI_CHARSET_UNICODE : toute les chaînes de caractères sont en Unicode (versions de Oracle >= 9i)
- OCI_CHARSET_MIXED : Ordres SQL, métadatas en Ansi et données utilisateurs fournies et récupérées des requêtes en Unicode
=> Convention d'appel (MS Windows uniquement) :
- OCI_API : non défini (option par défaut)
- OCI_API : __sdtcall pour utiliser les dll précompilées
Sous Windows, pour utiliser OCILIB en ANSI, en utilisant les Dlls fournies, il suffit de créer un nouveau projet et :
- Inclure "ocilib.h"
- Définir OCI_API=__sdtcall dans les options du préprocesseur du projet
- Si toutes les variantes ocilib[x].lib sont disponibles pour le linker, il faut alors préciser quel version en insérant #pragma comment(lib, "ocilib[x].lib") dans un des fichiers du projet
Sous Linux, pour utiliser OCILIB en ANSI et avec un linkage des shared librairies à la compilation, il faut :
=>Inclure "ocilib.h"
=> Ajouter au makefile pour le compilo :
- -I/usr/local/include pour le header ocilib.h
- -DOCI_IMPORT_LINKAGE -DOCI_CHARSET_ANSI pour configurer ocilib
=> Ajouter au makefile pour le linker :
- -L/$ORACLE_HOME/lib -lclntsh pour les librairies Oracle
- -L/usr/local/lib -locilib pour la librairie OCILIB
IV. Se Connecter à Oracle
IV-A. Initialiser OCILIB
Avant tout chose, OCILIB doit être initialisée. Pour cela il faut, avant tout appel à une fonction de la librairie, appeler OCI_Initilalize().
Cette fonction initialise la librairie et prend en paramètres :
- [Optionnel] Une pointeur sur une fonction de gestion des erreurs
- [Optionnel] Un répertoire ou se trouve les librairies Oracle (si runtime loading et plusieurs clients Oracle installés)
- [Optionnel] Le mode d'initialisation. Actuellement, seule la valeur OCI_ENV_DEFAULT est supportée
| Exemple |
#include "ocilib.h"
int main()
{
if (OCI_Initialize(err_handler, NULL, OCI_ENV_DEFAULT) == FALSE)
return EXIT_FAILURE;
OCI_Cleanup();
return EXIT_SUCCESS;
}
|
En fin d'application, un appel à OCI_Cleanup() est nécessaire pour :
- Désallouer les objets non explicitement libérés
- Décharger les librairies Oracle dans le cas d'un runtime loading
IV-B. Connexions
Se connecter à une base de données Oracle s'effectue par la fonction OCI_CreateConnection() qui prend en paramètres :
- Le nom du service Oracle (alias Oracle, SID, ... en fonction des paramètres du client Oracle).
- Le nom du user Oracle sur lequel se connecter
- Le mot de passe du user Oracle
- Le mode de session (OCI_SESSION_DEFAULT, OCI_SESSION_SYSDBA, OCI_SESSION_SYSOPER)
Cette fonction de charge de se connecter au serveur, créer une session et d'établir une transaction. En cas de succès, elle renvoie un handle de connexion et l'application peut de suite exécuter des ordres SQL.
| Exemple |
OCI_Connection *cn;
cn = OCI_CreateConnection("Database", "user", "password", OCI_SESSION_DEFAULT);
if (cn != NULL)
{
printf("%s\n", OCI_GetVersionServer(cn));
OCI_FreeConnection(cn);
}
|
Pour fermer la connexion au serveur, il suffit d'appeler OCI_FreeConnection()
IV-C. Gestion des erreurs
OCILIB propose un mécanisme de gestion des erreurs basé sur la notion de callback qui sera déclenché dans les cas suivants :
- Erreur générée par OCI
- Erreur d'allocation mémoire
- Erreur internes à OCILIB
Le prototype de la fonction à fournir est le suivant :
typedef void (*POCI_ERROR) (OCI_Error *err);
|
Un handle sur un object OCI_Error est alors fourni à la fonction. La librairie fournit des fonctions pour accéder aux propriétés de l'erreur générée.
| Exemple d'une fonction de gestion des erreurs qui se contente d'afficher à l'écran une erreur SQL : |
void ErrorHandler(OCI_Error *err)
{
int sql_code = OCI_ErrorGetOCICode(err);
if (sql_code != 0)
{
printf( "Code : ORA-%05i\n"
"Msg : %s\n",
sql_code, OCI_ErrorGetString(err));
}
}
|
IV-D. Transactions
OCILIB supporte les mécanismes de transactions (locales et globales) proposées par Oracle via OCI.
Par défaut, une fois que l'application s'est connectée à Oracle, une transaction locale est créée.
Toute modification de données (insert, update, delete) n'est pas visible des autres sessions tant que l'on a pas explicitement validé les modifications par un appel à OCI_Commit().
Si l'application ne veut pas valider ses modifications et donc annuler les modifications de données effectuées depuis la dernière validation (ou par défaut depuis la connexion au serveur), elle doit utiliser OCI_Rollback().
Il y des cas ou les simples transactions locales par défaut ne suffisent pas :
- gérer une connexion en lecture seule par exemple
- gérer une transaction globale dans un environnement distribué
- etc,..
Dans ces cas là, OCILIB propose une gestion explicite des transactions où il est possible de créer une transaction et de l'associer à un handle de connexion.
IV-E. Types de donnée supportés
OCILIB supporte tous les types de données fournis par Oracle, à l'exception des références et collections.
Liste des types supportés :
- Tous les types scalaires (strings, numériques, flottants, ..) : CHAR/NCHAR, VARCHAR2/NVARCHAR2, NUMBER, FLOAT, ...
- Types binaires : RAW, LONG RAW, VARRAW, ..
- Larges Objects (Lobs et Files) : BLOB, CLOB, NCLOB, BFILE, CFILE
- Types LONGs : LONG, VAR LONG, LONG RAW, ...
- Date, Timestamps et Intervals : DATE, TIMESTAMP (tous), INTERVAL (tous)
- PL/SQL types : Ref cursors et Nested Tables
- Names Types : User types, XmlType, ....
- ROWIDs
Tous ces types supportés peuvent être liés à des statements ou être récupérés d'un select.
| TYPE Oracle |
Type OCILIB |
| Strings : CHAR/NCHAR, VARCHAR2/NVARCHAR2, .... |
dtext * (char * or wchar_t * selon le build) |
| Numbers sans précision (entiers) : INT, NUMBER, ... |
shorts, ints, big ints (signed/unsigned) |
| Flottants : FLOAT, REAL, NUMBER(X,Y), .... |
double |
| RAW |
void * |
| LONG, LONG RAW, VARRAW, .. |
OCI_Long |
| BLOB / CLOB / NCLOB |
OCI_Lob |
| DATE |
OCI_Date |
| TIMESTAMP, TIMESTAMP_TZ, TIMESTAMP_LTZ |
OCI_Timestamp |
| INTERVAL, INTERVAL_YM, INTERVAL_DS |
OCI_Interval |
| PL/SQL Ref Cursors |
OCI_Statement |
| PL/SQL Nested Tables |
OCI_Statement |
| User Types |
OCI_Object |
| ROWID |
dtext * (char * or wchar_t * selon le build) |
V. Exécuter des ordres SQL
V-A. SQL Statements
Afin d'exécuter des ordres SQL sur la base de données, il faut créer un object SQL statement via la fonction OCI_CreateStatement().
Un fois cet objet crée, il est possible d'exécuter des requêtes par la fonction OCI_ExecuteStmt().
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "delete from my_table where code is null");
printf("%d row deleted", OCI_GetAffectedRows(st));
OCI_Commit(cn);
OCI_FreeStatement(st);
|
OCI_ExecuteStmt() a donc préparé et exécuté l'ordre SQL et OCI_GetAffectedRows() a récupéré le nombre de lignes supprimées dans la table.
Une fois que le statement n'est plus utile, un appel à OCI_FreeStatement() libère toutes les ressources associées (resultset, ...).
Une object OCI_Statement peut être réutilisé pour exécuter autant d'ordres SQL que nécessaire.
V-B. Lier des variables
Dans l'exemple précédent, la requête était simple et ne nécessitait aucune variable d'entrée. Souvent, il est nécessaire de fournir à une requête des valeurs d'entrées non connues à l'avance. De plus, il est utile de pouvoir exécuter plusieurs fois un même ordre SQL avec des valeurs différentes sans pour autant avoir à faire repréparer le SQL par Oracle afin d'optimiser les performances.
Il est donc possible de lier des variables du programme en fournissant leur adresse au statement.
Dans ce cas, il faut :
- Préparer le SQL par OCI_Prepare()
- Lier les variables avec les fonctions OCI_bindXXX() ou XXX est le type de donnée
- Exécuter le SQL par OCI_Execute()
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
int code;
st = OCI_CreateStatement(cn);
OCI_Prepare(st, "delete from test_fetch where code = :code");
OCI_BindInt(st, ":code", &code);
code = 3;
OCI_Execute(st);
printf("%d row deleted"; OCI_GetAffectedRows(st));
code = 56;
OCI_Execute(st);
printf("%d row deleted"; OCI_GetAffectedRows(st));
OCI_Commit(cn);
|
Pour faire une insertion de masse, on peut procéder de la manière suivante :
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
int code;
char name[30];
char value[20];
st = OCI_CreateStatement(cn);
OCI_Prepare(st, "insert into my_table values(:code, :name, :value)");
OCI_BindInt(st, ":code", &code);
OCI_BindString(st, ":name", name, 30);
OCI_BindString(st, ":value", value, 20);
for (code = 1; code < 10000; code++);
{
sprintf(name, "name %i", code);
sprintf(value, "value %i", code);
OCI_Execute(st);
}
OCI_Commit(cn);
|
Néanmoins, Oracle supporte les "bulks operations" (cf. chapitre VI) qui permettent de manipuler des tableaux de variables et donnent des performances incomparables.
V-C. Récupérer le résultat d'un select
Récupérer le résultat d'une requête est très simple. Il suffit une fois le statement exécuté via OCI_Execute() ou OCI_ExecuteStmt() d'appeler la fonction OCI_GetResultSet() qui retourne un handle sur un objet OCI_Resultset.
Ce resultset comprend 2 choses :
- La description des colonnes retournées par la requête
- Les données (lignes de résultats)
Afin d'accéder aux valeurs de chaque colonne du resultset, il faut:
- Parcourir le resultset par un appel à OCI_FetchNext() qui retourne TRUE tant qu'il y a des lignes de résultat
- Appeler une des fonctions OCI_GetXXX() en précisant l'index de la colonne (ou son nom) et où XXX est le type : Int, String, Date, ...
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select * from test_fetch");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
printf("code: %i, action : %s, price : %g, date %s\n",
OCI_GetInt(rs, 1) , OCI_GetString(rs, 2),
OCI_GetDouble(rs,3), OCI_GetString(rs,4));
}
printf("\n%d row(s) fetched\n", OCI_GetRowCount(rs));
|
OCI_GetRowCount() donne le nombre de lignes du resultset déjà parcourues.
Le même exemple avec un accès aux colonnes par leur nom :
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select * from test_fetch");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
printf("code: %i, action : %s, price : %g, date %s\n",
OCI_GetInt2(rs, "CODE") , OCI_GetStrin2g(rs, "ACTION"),
OCI_GetDouble2(rs,"PRICE"), OCI_GetString2(rs,"DATE"));
}
printf("\n%d row(s) fetched\n", OCI_GetRowCount(rs));
|
Un objet statement peut être réutilisé autant de fois que voulu. De plus, il est possible d'en créer plusieurs et de les imbriquer.
| Exemple |
OCI_Connection *cn;
OCI_Statement *st1, *st2;
OCI_Resultset *rs;
st1 = OCI_CreateStatement(cn);
st2 = OCI_CreateStatement(cn);
int code;
OCI_ExecuteStmt(st1, "select code from my_table");
OCI_Prepare(st2, "delete from my_table2 where code_ref = :code");
OCI_Bind(st2, ":code", &code);
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
code = OCI_GetInt(rs, 1);
OCI_Execute(st2);
}
|
Il est possible de récupérer les informations relatives (metadatas) à chaque colonne du resulset (colonnes sélectionnées par la requête). Pour cela :
- Un appel à OCI_GetColumnCount() permet de savoir le nombre de colonnes contenues dans le resultset
- Un appel à OCI_GetColumn() en précisant l'index (position dans le select) permet de récupérer un handle sur un objet OCI_Column
- Toute les propriétés de la colonne sont accessibles par une série de fonctions du type OCI_GetColumnXXX() où XXX est la propriété
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
OCI_Column *col;
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select * from my_table");
rs = OCI_GetResultset(st);
nb = OCI_GetColumnCount(rs);
for(i = 1; i <= nb; i++)
{
col = OCI_GetColumn(rs, i);
printf("%-20s%-10s%-8i%-8i%-8i%-s\n",
OCI_GetColumnName(col),
OCI_GetColumnSQLType(col),
OCI_GetColumnSize(col),
OCI_GetColumnPrecision(col),
OCI_GetColumnScale(col),
OCI_GetColumnNullable(col) == TRUE ? "Y" : "N");
}
|
V-D. PL/SQL blocks
Des variables peuvent être liées en entrée comme en sortie.
Pour exécuter du PL/SQL, il suffit d'englober le code PL/SQL par un "begin" en début et un "end;" en fin de block.
| Exemple d'un bloc PL/SQL |
OCI_Connection *cn;
OCI_Statement *st;
st = OCI_CreateStatement(cn);
OCI_Prepare(st, "begin :n := trunc(sysdate+1)-trunc(sysdate-1); end;");
OCI_BindInt(st, ":n", &n);
OCI_Execute(st);
printf("Result : %i\n", n);
|
Exemple de récupération d"un cursor PL/SQL et fetch de son résultat dans le programme C :
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
st = OCI_CreateStatement(cn);
st2 = OCI_CreateStatement(cn);
OCI_Prepare(st, "begin open :c for select * from my_table; end;");
OCI_BindStatement(st, ":c", st2);
OCI_Execute(st);
rs = OCI_GetResultset(st2);
while (OCI_FetchNext(rs))
{
printf("mon nom est %s\n", OCI_GetString(rs, 1));
}
|
V-E. Support de la clause SQL RETURNING
Oracle propose une fonctionnalité très intéressante qui est la clause SQL "returning into".
L'utilisation de cette clause dans un DML (insert/update/delete) permet de combiner 2 requêtes en 1, réduisant ainsi le nombre d'allers-retours entre le client et le serveur.
L'ajout de cette clause permet de sélectionner les lignes affectées par le DML au sein de la même requête. Ce qui est pratique, notamment dans le cas de delete car les valeurs supprimées sont ainsi récupérables !
OCILIB implémente cette fonctionnalité en créant un objet OCI_Resultset à partir des colonnes sélectionnées dans la clause RETURNING. Ce resultset est ensuite manipulable comme un resultset classique.
L'Array Interface est également compatible avec cette clause. Dans ce cas, chaque entrée du tableau peut affecter différentes lignes. OCILIB crée alors un resultset pour chaque entrée du tableau.
Par exemple, si le tableau utilisé pour lier des variables au statement contient 100 éléments, l'application commence par récupérer le premier resulset par un appel à OCI_GetResulset(). Une fois ce resultset parcouru, le resultset suivant est récupérable par un appel à OCI_GetNextResultset().
OCILIB ne pré-parse jamais les requêtes SQL (pour des raisons de performances) et donc ne peut déterminer les champs (ainsi que leur type) utilisés dans la clause RETURNING.
Il faut donc que l'application indique explicitement les colonnes de la clause RETURNING par les fonctions OCI_RegisterXXX() ou XXX est le type de données. OCILIB construit alors les resultsets sur la base des colonnes ainsi déclarées.
| Exemple de sélection des objets d'une table : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
st = OCI_CreateStatement(cn);
OCI_Prepare(st, "update my_table set content = content || to_char(code) returning code, content into :i, :s");
OCI_RegisterInt(st, ":i");
OCI_RegisterString(st, ":s", 50);
OCI_Execute(st);
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
printf("code : %i - content : %s\n", OCI_GetInt(rs, 1), CI_GetInt(rs, 2));
}
printf("count : %i\n", OCI_GetRowCount(rs));
OCI_Commit(cn);
|
V-F. Contrôler les statements
OCILIB propose une série de fonctions permettant de personnaliser les comportements d'un statement.
Par exemple, il est possible de spécifier :
- Le nombre de lignes pré-fetchées par le client Oracle afin de diminuer le nombre de roundtrips avec le serveur
- Le nombre de lignes fetchées internalement par OCILIB afin de réduire les appels OCI
- Le mode de liaison des variables : par position ou par nom
- Le format de date par défaut
- Etc, ..
VI. Bulk Operations
Les "bulk operations" sont une fonctionnalité très puissante supportée par Oracle qui permet d'effectuer des mises à jour massives (insert/update/update) de tables dans des temps records.
Dans un schéma classique, si l'on souhaite insérer 10000 lignes dans une table par exemple, il est possible de :
- préparer la requête
- lier les variables
- boucler de 1 à 10000 pour exécuter le SQL en ayant à chaque tour de boucle mis à jour les valeurs.
Ce schéma va générer 10000 appels d'exécution de la requête et donc 10000 allers / retours avec le serveur pour :
- Transmettre les valeurs
- Exécuter de nouveau le SQL
Tous ces appels et allers/retours réseau sont couteux en termes de performances ! Si l'on souhaite insérer 1 millions de lignes, cela peut être long !
Oracle propose donc une solution simple qui permet d'obtenir des perfomances optimales : OCI Interface Array
VI-A. Oracle Array interface
Dans l'exemple précédent, le trafic réseau généré par un nombre important d'exécution d'une même requête est la source principale de la lenteur finale du traitement.
Oracle propose de minimiser ce trafic en lui fournissant des tableaux de variables. Au lieu de fournir 1 variable et envoyer 10000 fois sa valeur au serveur, on fournit un tableau de 10000 valeurs qui est envoyé en une seule fois.
Ce système permet de réduire le temps de traitement par des facteurs de 3 à 5 chiffres au minimum sur des gros volumes.
OCILIB supporte ce mécanisme et donne la possibilité de lier des tableaux de n'importe quel type supporté par OCILIB (excepté OCI_Long et OCI_Statement)
VI-B. Exemples
| Exemple pour insérer d'un seul coup 10000 lignes dans une table : |
OCI_Connection *cn;
OCI_Statement *st;
int tab_int[10000];
char tab_str[10000][21];
OCI_Prepare(st, "insert into my_table values(:i, :s)");
OCI_BindArraySetSize(st, 10000);
OCI_BindArrayOfInts(st, ":i", tab_int);
OCI_BindArrayOfStrings(st, ":s", tab_str, 20);
for(i=0;i<10000;i++)
{
tab_int[i] = i+1;
sprintf(tab_str[i],"Name %d",i+1);
}
OCI_Execute(st);
printf("row processed : %d\n", OCI_GetAffectedRows(st));
|
VI. Utiliser les Larges Objects
Oracle 8 a introduit les LOBs (Larges Objects) afin de pouvoir stocker et manipuler des objets de grosse taille (> plusieurs Go) afin de répondre aux besoins de stockage de gros fichiers (multimédia, vidéo, ...) et de données brutes au sein de la DB.
Il existe fondamentalement 2 types de large objects :
- Les BLOB/CLOBS : objets stockés sous forme binaire (BLOBS) / textuelle (CLOBS)
- Les BFILES : objets non stockés au sein de la DB. Il s'agit de pointeurs sur des fichiers accessibles par le serveur (sur le même système de fichier ou non) et qui sont manipulables par l'application cliente
OCILIB supporte intégralement ces types de données.
De plus, OCILIB supporte également l'ancienne implémentation de LOBs : les types LONG, RAW et LONG RAW. Ces types, toujours maintenus par Oracle, sont les "ancêtres" des LOBs et représentaient dans les versions antérieures (jusqu'à Oracle 7) l'unique moyen de stocker des gros volumes (mais plus limités) dans la DB.
Les types LONGs sont toujours actuellement très employés dans les bases et applications Oracle.
C'est pourquoi OCILIB propose une API très proche de celles des LOBs (alors que la sous couche OCI de gestion des LONGs est très différente de celle des LOBs).
VI-A. Internal Objects (LOBs)
Les BLOB/CLOB sont stockés dans la base de données. Leur utilisation diffère de celles des autres types de données. En effet, une simple requête SQL ne suffit pas à lire ou écrire dans un champ de plusieurs centaines de Mo.
OCILIB propose tout une série de fonctions permettant de manipuler très simplement ces objets (OCI_Lob *).
| Par exemple, pour insérer le contenu d'un fichier dans un champ de type BLOB d'une table : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Lob *lob;
FILE *f;
unsigned char buffer[1024];
int size;
/* ... */
st = OCI_CreateStatement(cn);
f = fopen("My file", "rb");
if (f != NULL)
{
fseek (f , 0 , SEEK_END);
size = ftell(f);
rewind (f);
printf("\nTotal bytes to write : %d\n", size);
lob = OCI_LobCreate(cn, OCI_BLOB);
OCI_Prepare(st, "insert into my_blob_table(code, content) values (1, :data)");
OCI_BindLob(st, ":data", lob);
OCI_Execute(st);
/* write data into table by chunks of 1024 bytes */
while ((n = fread(buffer, 1, sizeof(buffer), f)))
{
OCI_LobWrite(lg, buffer, n);
}
printf("\nTotal bytes written : %s\n", OCI_LobGetLenght(lob));
fclose(f);
OCI_Commit(cn);
OCI_LobFree(lob);
}
/* ... */
|
Par exemple, pour lire le contenu d'un fichier texte ANSI stocké dans un champ de type CLOB d'une table :
| Exemple |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
OCI_Lob *lob;
FILE *f;
char buffer[128];
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select content from my_blob_table where code = 1");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
lob = OCI_GetLob(rs, 1);
while ((n = OCI_LobRead(lob, buffer, sizeof(buffer)-1))
{
buffer[n] = 0;
printf("%s\n", buffer);
}
}
|
VI-B. External Objects (FILEs)
Les objets de type FILE (OCI_File *) sont des références à des fichiers externes à la DB qui peuvent être ouverts pour lecture par le serveur et dont le contenu peut être récupéré par l'application cliente.
Les objets FILEs sont similaires aux LOBs et donc leur API est similaire sauf que les FILEs sont en lecture seule.
| Exemple de lecture d'un champ BILE d'un table Oracle : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
OCI_File *file;
unsigned char buffer[1024];
int n;
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select value from my_bfile_table where code = 1");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
file = OCI_GetFile(rs, 1);
OCI_FileOpen(file);
printf("file size %d\n", OCI_FileGetSize(file));
printf("file dir %s\n", OCI_FileGetDirectory(file));
printf("file name %s\n", OCI_FileGetName(file));
while (n = OCI_FileRead(file, buffer, sizeof(buffer))
{
}
OCI_FileClose(file);
}
|
VI-C. Long Objects
OCILIB implémente les "anciens" LONGs (OCI_Long *) de manière similaire aux LOBs et FILEs. Les APIs de manipulation des champs LONGs sont donc très proches de celles des Larges Objects.
| Exemples pour stocker un fichier binaire en base et ensuite récupérer le contenu : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
OCI_Long *lg;
unsigned char buffer[1024];
FILE *f;
int n;
st = OCI_CreateStatement(cn);
f = fopen("file name", "rb");
if (f != NULL)
{
fseek (f , 0 , SEEK_END);
n = ftell(f);
rewind (f);
printf("\n%d bytes to write\n", n);
lg = OCI_LongCreate(st, OCI_BLONG);
OCI_Prepare(st, "insert into test_long_raw(code, content) values (1, :data)");
OCI_BindLong(st, ":data", lg, n);
OCI_Execute(st);
while ((n = fread(buffer, 1, sizeof(buffer), f)))
{
OCI_LongWrite(lg, buffer, n);
}
printf("\n%d bytes written\n", OCI_LongGetSize(lg));
fclose(f);
OCI_Commit(cn);
OCI_LongFree(lg);
}
OCI_ExecuteStmt(st, "select content from test_long_raw where code = 1");
OCI_SetLongMaxSize(st, 100000);
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
lg = OCI_GetLong(rs, 1);
while ((n = OCI_LongRead(lg, buffer, sizeof(buffer))))
{
}
printf("\n%d bytes read\n", OCI_LongGetSize(lg));
}
|
VIII. Manipuler les Named Types
OCILIB poropose les "Named types", c'est à dire des types de données nommés crées par l'utilisateur ou fournis par Oracle. Un "TYPE" oracle peut vu comme l'équivalent d'une structure C.
Il s'agit d'une agrégation de champs de types scalaires ou agrégés.
| Exemple de création d'un type de données : |
create type t_produit as object
(
code number,
libelle varchar2(30),
prix number(5,3),
reference varchar2(100)
);
create table ventes_produits
(
product t_produit,
nombre int,
date_vente date
);
|
OCILIB permet de :
- créer des instances d'objet
- de lier des objets à requêtes SQL
- sélectionner les objets d'une table
- manipuler les attributs d'un objet
VIII-A. Instances autonomes
Afin de créer une instance autonome, il faut récupérer le descripteur du type concerné. Cela se réalise par un appel à OCI_SchemaGet().
| Exemple de création d'une instance d'un objet pour une insertion dans une table : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Object *prod;
OCI_Date *date;
int qte;
prod = OCI_ObjectCreate(cn, OCI_SchemaGet(cn, "t_product", OCI_SCHEMA_TYPE);
date = OCI_DateCreate(cn);
qte = 356;
OCI_DateSysDate(date);
OCI_ObjectSetInt(prod, "CODE", 1);
OCI_ObjectSetString(prod, "LIBELLE", "USB KEY 2go");
OCI_ObjectSetDouble(prod, "PRIX", 12.99);
OCI_ObjectSetString(prod, "REFERENCE", "A56547WSAA");
OCI_Prepare(st, "insert into ventes_produits values(:p, :n, :d)");
OCI_BindObject(st, ":p", prod);
OCI_BindInt(st, ":n", &qte);
OCI_BindDate(st, ":d", date);
OCI_Execute(st);
printf("\n%d row(s) inserted\n", OCI_GetAffectedRows(st));
OCI_Commit(cn);
OCI_ObjectFree(obj);
|
VIII-B. Sélection d'objets
Les objets peuvent être sélectionnés de la même façon que les types de donnée SQL.
| Exemple de sélection des objets d'une table : |
OCI_Connection *cn;
OCI_Statement *st;
OCI_Resultset *rs;
OCI_Object *prod;
OCI_Date *date;
int qte;
OCI_ExecuteStmt(st, "select * from ventes_produits");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
prod = OCI_GetObject(rs, 1);
qte = OCI_GeInt(rs, 2);
date = OCI_GetDate(rs, 3);
printf("Produit : %s, Ventes : %i\n", OCI_ObjectGetString(prod, "LIBELLE"), qte);
}
|
IX. Manipulation des dates et timestamps
IX-A. Dates
OCILIB permet de manipuler les dates Oracle (date/heure) avec facilité.
Exemple où une date est récupérée d'une table, puis affichée, incrémentée de 5 jours et 2 mois et enfin réaffichée :
| Exemple |
OCI_Statement *st;
OCI_Resultet *st;
OCI_Date *dt;
char str[100];
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "select mon_champs_date from my_table");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
dt = OCI_GetDate(rs, 1);
OCI_DateToText(dt, "DD/MM/YYYY HH24:MI:SS", sizeof(str)-1, str);
printf("Date is %s\n", str);
OCI_DateAddDays(dt, 5);
OCI_DateAddMonths(dt, 2);
OCI_DateToText(dt, "DD/MM/YYYY HH24:MI:SS", sizeof(str)-1, str);
printf("Date + 5 days and 2 months is %s\n", str);
}
|
IX-B. Timestamps
Les timestamps sont des extensions au type DATE permettant :
- de gérer avec précision les dates grâce à la gestion des fractions de secondes
- de gérer les fuseaux horaires
Exemple où la date/heure courante est récupérée du serveur et affichée avec les fractions de secondes :
| Exemple |
OCI_Statement *st;
OCI_Timestamp *tm;
char str[100];
st = OCI_CreateStatement(cn);
OCI_ExecuteStmt(st, "SELECT CURRENT_TIMESTAMP FROM dual");
rs = OCI_GetResultset(st);
OCI_FetchNext(rs);
tm = OCI_GetTimestamp(rs, 1);
OCI_TimestampToText(tm, "DD/MM/YYYY HH24:MI:SS:FF3\n", 100, str, 3);
printf(str);
|
IX-C. Intervals
Les intervals sont des périodes de temps exprimées :
- soit en années et mois
- soit en jours, heures, minutes et secondes
Ils permettent d'effectuer des opérations sur les timestamps.
Exemple où la date/heure courante locale est récupérée dans la variable tm, puis incrémentée de 1 jour, 1 heure, 1 minute et 1 seconde puis affichée avec les fractions de secondes :
| Exemple |
OCI_Connection *cn;
OCI_Timestamp *tm;
OCI_Interval *itv;
char str[100];
|
| |