La documentation sur le net est celle pour le pilote ODBC de MySQL, MyODBC. Elle est très très
complète.
Chez MySQL
Autre site qui permet d'accéder à une doc complète:
Chez Microsoft
mais plus précisément ici
Mais on trouve également sur le site de sapdb, page des downloads, une archive nommée
FirstSteps.tar.gz avec des exemples de code pour ODBC mais aussi Java, Perl, embedded SQL.
Il convient également de regarder de près les fichiers sql.h, sqlext.h et sqltypes.h pour se faire une idée des prototypes, attributs possibles et types de données SQL et C. Il y a des différences entre l'API MyODBC et l'API ODBC pour MaxDB.
Dernier avertissement: je ne suis pas un pro du C ! J'en fait un peu parce que j'aime ça, alors le code...hein...il vaut ce qu'il vaut et j'espère que ce n'est pas trop mauvais! En tout état de cause je ne vais pas refaire le manuel MyODBC, juste donner quelques exemples qui fonctionnent, et pointer ce qui m'a paru important et/ou sur lequel j'ai buté.
On aura donc des lignes du type (paramètres et options non exhaustives):
CFLAGS = -DSQLODBC ODBCROOT = /usr/local/sapdb/indep_prog LIBS = -L$(ODBCROOT)/lib -lsqlod INC = -I$(ODBCROOT)/incl
#include <stdio.h>
#include <WINDOWS.H>
#include <sql.h>
#include <sqlext.h>
#define SAPDBRUNTIME "/usr/local/sapdb/indep_data/wrk"
#define MAX_MSG_LEN 200
#define LOGIN_TIMEOUT 20UL
#define MYTRACEFILE SAPDBRUNTIME"/odbc.log"
#define DRIVER_NAME "SAPDB"
typedef struct {
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
} Handles_t;
Handles_t *SQLConnectFunc (char *host_name,
char *user_name,
char *password,
char *db_name,
char *tracefilename)
{
SQLRETURN retour;
SQLHENV henv;
SQLHDBC hdbc;
UCHAR *server_string;
SQLCHAR ConnStringIn[256];
SQLCHAR ConnStringOut[1024];
SQLSMALLINT ConnStringLength;
Handles_t *h;
/* Allocation memoire */
if (!(h = (Handles_t *) malloc (sizeof (Handles_t))))
{
fprintf (stderr, "SQLConnectFunc(): Ne peut obtenir une structure Handles_t.\n");
return NULL;
}
/* allouer l'environnement de connexion */
if ((retour = SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE, &h->henv)) != 0)
{
fprintf (stderr, "SQLAllocHandle(): Allocation d'environnement: Échec.\n");
return NULL;
}
/* indiquer la version odbc utilisee */
if ((retour = SQLSetEnvAttr (h->henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0)) != 0)
{
fprintf (stderr, "SQLAllocHandle(): Parametrage d'environnement: Échec.\n");
return NULL;
}
/* Allouer la connexion */
if (( retour = SQLAllocHandle (SQL_HANDLE_DBC, h->henv, &h->hdbc)) != 0)
{
fprintf (stderr, "SQLAllocHandle(): Allocation de connexion: Échec.\n");
SQLFreeHandle (SQL_HANDLE_ENV, h->henv);
return NULL;
}
/* definir quelques parametres */
SQLSetConnectAttr (h->hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) LOGIN_TIMEOUT, 0);
SQLSetConnectAttr (h->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, 0);
SQLSetConnectAttr (h->hdbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) CONNECTION_TIMEOUT, 0);
/* connexion par SQLConnect
* ATTENTION: c'est SQLConnect ou SQLDriverConnect
*/
if (!(server_string =
(UCHAR *) malloc ((strlen (host_name) + strlen (db_name) + 2) * sizeof (char))))
{
fprintf (stderr, "Ne peut obtenir une chaîne dsn hote:base.\n");
return NULL;
}
sprintf (server_string, "%s:%s", host_name, db_name);
retour = SQLConnect (hdbc, server_string, SQL_NTS,
(UCHAR *) user_name, SQL_NTS,
(UCHAR *) password, SQL_NTS);
if (retour != SQL_SUCCESS && retour != SQL_SUCCESS_WITH_INFO)
{
SQLErrorMsg (SQL_HANDLE_DBC, h->hdbc);
return NULL;
}
/* Connexion par SQLDriverConnect */
sprintf (ConnStringIn, "DRIVER={%s}; UID=%s; PWD=%s; SERVERDB=%s; "
"SERVERNODE=%s; TRACEFILENAME=%s",
(UCHAR *) DRIVER_NAME,
(UCHAR *) user_name,
(UCHAR *) password,
(UCHAR *) db_name,
(UCHAR *) host_name,
(UCHAR *) MYTRACEFILENAME);
retour = SQLDriverConnect (h->hdbc,
NULL,
ConnStringIn,
SQL_NTS,
ConnStringOut,
sizeof (ConnStringOut),
&ConnStringLength,
SQL_DRIVER_COMPLETE);
if (retour != SQL_SUCCESS && retour != SQL_SUCCESS_WITH_INFO)
{
SQLErrorMsg (SQL_HANDLE_DBC, h->hdbc);
SQLFreeHandle (SQL_HANDLE_DBC, h->hdbc);
SQLFreeHandle (SQL_HANDLE_ENV, h->henv);
return NULL;
}
/* fin de la connexion */
/* Allocation d'instruction */
if ((retour = SQLAllocHandle (SQL_HANDLE_STMT, h->hdbc, &h->hstmt)) != 0)
{
fprintf (stderr, "SQLAllocHandle(): Allocation d'instruction: Échec.\n");
SQLDisconnectFunc (h);
return NULL;
}
SQLSetStmtAttr (h->hstmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER) QUERY_TIMEOUT, 0);
return handle;
}
En fait mon code n'est pas tout à fait identique puisque les variables de connexion sont définies
par directives '#define' au préprocesseur, tandis que MYTRACEFILENAME est soumise à
compilation conditionnelle avec un CFLAGS -DODBCTRACE rajouté au Makefile.
On ne peut faire passer la variable TRACEFILENAME au pilote qu'au travers de
SQLDriverConnect(). Voir sur le wiki (cf 1) à ODBCTRACE
De même il est possible de passer au pilote les options IsolationLevel et SQLMode de
la même manière. La doc indique que les écritures dans le fichier de log ralentissent la vitesse
d'exécution, donc que cette fonctionnalité est plutôt à désactiver (débogage).
Mais j'ai testé les deux fonctions alors je mets le code des deux.
La demande de connexion commence donc par des demandes d'allocation mémoire pour l'environnement, la connexion et les instructions qui vont suivre.
Les allocations se font avec SQLAllocHandle(), le paramétrage se fait avec
SQLSetConnectAttr() pour le paramétrage de la connexion et SQLSetStmtAttr() pour
celui de l'instruction. QUERY_TIMEOUT et CONNECTION_TIMEOUT sont des constantes que
j'ai défini personnellement.
Le fichier d'en-tête sqltypes.h définit tout un tas de types portables pour le langage C:
UCHAR est un type qui correspond à un unsigned char, le type signé étant alors SCHAR.
On y trouve également la définition des types pour l'API:
SQLSMALLINT correspond à un short avec son pendant non signé SQLUSMALLINT,
SQLPOINTER pour un void *, SQLCHAR à un unsigned char, la valeur de retour
SQLRETURN à un SQLSMALLINT soit un short (la valeur de retour d'une fonction est
parfois exprimée en RETCODE qui correspond à la même chose).
void SQLDisconnectFunc (Handles_t *h)
{
SQLDisconnect (h->hdbc);
SQLErrorMsg (SQL_HANDLE_DBC, h->hdbc);
/* normalement tout est libere a la deconnexion
* donc on ne verifie que le retour de SQLDisconnect()
*/
SQLFreeHandle (SQL_HANDLE_STMT, h->hstmt);
SQLFreeHandle (SQL_HANDLE_DBC, h->hdbc);
SQLFreeHandle (SQL_HANDLE_ENV, h->henv);
free (h);
}
Pour se déconnecter on provoque la rupture de connexion avec SQLDisconnect() et on libère la
mémoire avec SQLFreeHandle().
int SQLErrorMsg (SQLSMALLINT handletype, SQLHANDLE handle)
{
SDWORD sqlcode;
SWORD realmsglen;
UCHAR sqlstate[10];
UCHAR msg[MAX_MSG_LEN + 1];
SQLGetDiagRec (handletype, handle, 1, sqlstate, &sqlcode, msg, MAX_MSG_LEN, &realmsglen);
fprintf(stderr, "SQL State: %s\n", sqlstate);
fprintf(stderr, "SQL Error Code: %d\n", sqlcode);
fprintf(stderr, "SQL Message:\n%s\n\n", msg);
return 0;
}
On retrouve le type UCHAR mais aussi SWORD, pour Signed Word, un mot sur 2 octets donc
un short, tandis que SDWORD est un Signed Double Word donc un int ou un long signé sur 4
octets, les types non signés respectifs de ces derniers étants UWORD et UDWORD.
va_list va;
size_t taille_buffer;
UCHAR *buffer;
SQLRETURN retour;
SQLLEN nb;
const char *format = "INSERT INTO COURRIEL (msgid, maildate, mailfrom, subject, body) "
"VALUES ('%s', '%s', '%s', '%s', '%s')";
buffer = (UCHAR *) malloc (taille_buffer * sizeof (char));
va_start (va, format);
vsnprintf (buffer, taille_buffer, format, va);
va_end (va);
/* executer la requete */
retour = SQLExecDirect (hstmt, buffer, SQL_NTS);
if (retour != SQL_SUCCESS && retour != SQL_SUCCESS_WITH_INFO)
{
fprintf (stderr, "insertion(): Erreur.\n\n");
SQLErrorMsg (SQL_HANDLE_STMT, hstmt);
}
else
SQLRowCount (hstmt, &nb);
free (buffer);
On a là une requête directe avec SQLExecDirect() testée avec la fonction d'erreur.
SQL_NTS représente une chaîne terminée par un caractère null (Null Terminated String)
constante qui permet souvent de définir la longueur de la chaîne par sa terminaison. Une requête
directe est utilisée lorsque l'instruction se répète peu. Sinon on utilisera une
requête préparée qui compile l'instruction une fois et permet de gagner du temps à chaque passage.
On verra ça plus loin.
SQLRowCount() permet de tester le nombre de rangs concernés par la requête sur un insert, pas
sur un select. Le nombre de rangs est retourné dans un pointeur SQLLEN, un long. Il y a une
différence avec l'API MyODBC pour laquelle cette variable doit pointer sur un SQLINTEGER.
Attention: Il faut protéger le guillemet simple lors de toute insertion sinon la sanction
tombe: Invalid end of SQL Statement. On le protège avec un autre guillemet simple. Ainsi on
écrira:
dbmcli> sql_execute INSERT INTO matable (monchamp) VALUES ('J''aime l''apostrophe')
Ce qui signifie qu'en cas d'insertion automatique il faudra écrire une routine qui protège ce caractère si il est susceptible d'être présent.
Il existe un Statement Attribute qui permet de demander au pilote de scanner les chaînes à la
recherche de caractères à protéger (si j'ai bien compris): SQL_ATTR_NOSCAN qui est par défaut à
OFF (attribut SQL_NOSCAN_OFF) donc scan par défaut. Je suppose qu'on gagne un peu de
vitesse en désactivant le scan si on est sur de son coup. Pas essayé.
char *p;
UCHAR Inbuffer[81];
UCHAR Outbuffer[81];
SQLRETURN retour;
SQLLEN Outbufferlen;
SQLLEN Inbufferlen;
if (SQLPrepare (stmt, "SELECT msgid FROM courriel WHERE msgid = ? AND fils_de = '<head>'",
SQL_NTS) != SQL_SUCCESS)
SQLErrorMsg (SQL_HANDLE_STMT, stmt);
if (SQLBindParam (stmt, 1, SQL_C_CHAR, SQL_CHAR, 80, 0, Inbuffer, &Inbufferlen) != SQL_SUCCESS)
SQLErrorMsg (SQL_HANDLE_STMT, stmt);
/* boucle de récupération */
while (condition)
{
/* une valeur est récupérée dans Inbuffer */
* faire le select
* si select ok, break et renvoyer la valeur
*/
if (SQLExecute (stmt) != SQL_SUCCESS)
SQLErrorMsg (SQL_HANDLE_STMT, stmt);
/* parametrer la valeur du resultat */
SQLBindCol (stmt, 1, SQL_C_CHAR, Outbuffer, sizeof (Outbuffer), &Outbufferlen);
if ((retour = SQLFetch (stmt)) == SQL_NO_DATA)
continue;
if (retour != SQL_SUCCESS && retour != SQL_SUCCESS_WITH_INFO)
{
fprintf (stderr, "rechercher_parent(): Erreur\n\n");
SQLErrorMsg (SQL_HANDLE_STMT, stmt);
}
return (p = (char *) Outbuffer);
}
/* rien trouvé */
return NULL;
C'est quoi tout ce charabia ? En premier lieu on envoie une demande de préparation d'instruction au
serveur avec SQLPrepare(). Le ? n'est pas une valeur passée à l'instruction mais la
modélisation d'un paramètre variable. On ne le met donc pas entre guillemets.
Puis, avec SQLBindParam() on indique quel va être le format du paramètre. Référez vous au
manuel pour les arguments de la fonction: en gros on dit que le premier paramètre (il n'y en a qu'un
ici) est un char[] et il fait référence également à une chaîne dans la table (SQL_CHAR)
dont la longueur maxi est de 80 caractères (défini à la création de table), ce n'est pas un nombre
donc pas de précision à apporter (0), le paramètre sera dans Inbuffer et on retrouvera la longueur
du paramètre dans Inbufferlen. Il ne faut pas mettre de null dans ce dernier qui est de type
SQLEN (SQLINTEGER chez MyODBC), et le nom de la fonction est différent de la fonction
de même type chez MyODBC (SQLBindParameter()).
Ensuite la boucle est sensée récupérer à chaque itération une nouvelle valeur pour Inbuffer. Je n'ai
pas mis le code qui ne sert à rien. La requête préparée est alors envoyée pour exécution avec
SQLExecute(). On paramètre ensuite avec SQLBindCol() le format de sortie de la requête
pour chaque champ, résultat qui sera dans Outbuffer. Le 5ème argument de la fonction indique la taille
de la variable de réception y compris le caractère null final dans le cas d'une chaîne. De même
que précédemment concernant le dernier argument, un pointeur sur un SQLLEN est indispensable.
On va sur l'enregistrement avec SQLFetch() et on teste la valeur de retour. En cas de
réponse on retourne la réponse. Sinon on recommence et c'est là qu'est l'utilité du SQLPrepare()
qui permet de réutiliser l'instruction.
En fait un curseur est utilisé en interne ce qui m'a posé un problème pour la suite du fait de mon
inexpérience. Car à la suite de ce select, je dois faire, si je reçois quelque chose, un insert dans
la table et là, patatras Invalid cursor state ! Ce qui s'arrange avec:
SQLCloseCursor (stmt)
qui ferme le curseur incriminé. Du coup dans mes recherches frénétiques pour solutionner mon petit
souci j'ai trouvé la fonction SQLFreeStmt() qui peut faire la même chose mais aussi une
"remise à zéro" du SQLHSTMT passé en argument (libération des ressources, fermeture du
curseur, annulation des opérations en attente) en fonction de l'option passée à la fonction.
A priori, compte tenu de ce que j'ai pu voir dans mes manips suivantes, j'aurais pu allouer un nouvel
SQLHSTMT pour le select, mais il est probable que le code est plus rapide ainsi car
l'allocation est couteuse en temps.
Voilà un petit bout de code pour illustrer la manip. Il est plein de bourdes (!) car j'avais
bridé le select avec une clause WHERE ROWNO < 2 pour tester, puis, bêtement, je l'ai débridé
alors que je n'arrivais pas à trouver comment me sortir d'un Invalid cursor state. Et quand
j'ai trouvé c'est parti tout seul...et je n'ai plus eu l'occasion par conséquent de modifier le
code en fonction du nombre total de rangs à mettre à jour.
Handles_t *IDConnexion;
SQLHSTMT update_hstmt;
SQLHSTMT select_hstmt;
UCHAR select[256];
UCHAR cursorname[16];
UCHAR msgid[81];
UCHAR body[7000];
UCHAR *bodyconv;
UCHAR update[7100];
SQLLEN msgidlen, bodylen;
SWORD cursornamelen;
SQLRETURN retour;
const char update_format[] = "UPDATE COURRIEL SET body = '%s' WHERE CURRENT OF %s";
/* un nouvel SQLHSTMT pour le update */
if ((retour = SQLAllocHandle (SQL_HANDLE_STMT, hdbc, &update_hstmt)) != 0)
{
fprintf (stderr, "SQLAllocHandle(): Allocation d'instruction: Échec.\n");
SQLDisconnectFunc (IDConnexion);
exit (-1);
}
/* curseur pour le select */
SQLSetCursorName (select_hstmt, "UPDCURS", SQL_NTS);
/* select */
sprintf (select, "SELECT msgid, body FROM COURRIEL "
"WHERE body LIKE '%%¶%%' FOR UPDATE OF body FOR REUSE");
if ((retour = SQLExecDirect (select_hstmt, select, SQL_NTS)) != SQL_SUCCESS)
{
fprintf (stderr, "SQLExecDirect (): Erreur.\n");
SQLErrorMsg (SQL_HANDLE_STMT, select_hstmt);
exit (-1);;
}
SQLBindCol (select_hstmt, 1, SQL_C_CHAR, msgid, (SDWORD) sizeof (msgid), &msgidlen);
SQLBindCol (select_hstmt, 2, SQL_C_CHAR, body, (SDWORD) sizeof (body), &bodylen);
while (1)
{
if ((retour = SQLFetchScroll (select_hstmt, SQL_FETCH_NEXT, 0)) == SQL_NO_DATA)
break;
if (retour != SQL_SUCCESS && retour != SQL_SUCCESS_WITH_INFO)
{
fprintf (stderr, "SQLFetchScroll(): Erreur.\n");
SQLErrorMsg (SQL_HANDLE_STMT, select_hstmt);
break;
}
bodyconv = (UCHAR *) convertir (body);
sprintf (update, update_format, bodyconv, cursorname);
if ((retour = SQLExecDirect (update_hstmt, update, SQL_NTS)) != SQL_SUCCESS
&& retour != SQL_SUCCESS_WITH_INFO)
{
SQLErrorMsg (SQL_HANDLE_STMT, update_hstmt);
break;
}
}
SQLCloseCursor (select_hstmt);
A l'appel de la fonction j'avais déjà initialisé un SQLHENV (pas visible ici), un
SQLHDBC (hdbc), et un SQLHSTMT (select_hstmt). Il en faut un de plus pour le
UDDATE, il n'est pas possible d'utiliser le même, d'où mon erreur du "Invalid cursor state".
On prépare une chaine de requête pour générer un résultat FOR UPDATE OF et on envoie, après
avoir nommé un curseur avec SQLSetCursorName().
On récupère avec SQLFetchScroll() les résultats de la requête. Une bourde potentielle ici,
je pense que j'aurais du utiliser un curseur de bloc pour récupérer les enregistrements par blocs,
mais j'ai su après que ce pouvait être possible.
Le champ body récupéré fait l'objet d'une conversion (fonction convertir() qui vise à changer
les caractères ¶ en sauts de lignes, et d'autres bricoles sur des caractères en hexa) et je l'envoie
dans l'update. Grosse bourde, puisque j'avais 237 enregistrements à mettre à jour, il eût fallu
utiliser une requête préparée. Passons...
A l'issue de la fonction, les 237 enregistrements sont mis à jour.
J'ai utilisé dans le petit bout de code suivant un curseur de colonnes (est ce la bonne traduction
?). C'est à dire qu'on va paramétrer des tableaux, chaque tableau devant stocker N valeurs si la
taille de l'échantillon ramené par passe est N. Chaque tableau représente donc une colonne possédant
N valeurs. Par conséquent on devra avoir autant de tableaux que de champs de tables ramenés par la
requête.
La requete utilisée fut:
SELECT msgid, fils_de, subject FROM JM.COURRIEL WHERE SUBJECT LIKE '%valeur%'
Il faut remarquer la syntaxe de dénomination de la table PROPRIÉTAIRE.NOM_TABLE, puisque
l'utilisateur qui se connecte (INVITE) n'est pas propriétaire, mais fut créé par le propriétaire JM
créateur des tables.
#define TAILLE_ECHANTILLON 20
SQLHSTMT hstmt;
SQLINTEGER NbEchantillonsParPasse;
SQLUSMALLINT RowStatusTab[TAILLE_ECHANTILLON], i;
SQLCHAR MsgidTab[TAILLE_ECHANTILLON][129];
SQLCHAR FilsdeTab[TAILLE_ECHANTILLON][129];
SQLCHAR SubjectTab[TAILLE_ECHANTILLON][129];
SQLLEN MsgidIndTab[TAILLE_ECHANTILLON];
SQLLEN FilsdeIndTab[TAILLE_ECHANTILLON];
SQLLEN SubjectIndTab[TAILLE_ECHANTILLON];
SQLRETURN retour;
/**** les proprietes du stmt pour une recup par blocs ****/
/* taille de l'echantillon par passe */
SQLSetStmtAttr (hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) TAILLE_ECHANTILLON, 0);
/* bind par colonne */
SQLSetStmtAttr (hstmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
/* le pointeur de nb d'enregistrements recuperes */
SQLSetStmtAttr (hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER) &NbEchantillonsParPasse, 0);
/* parametrage colonnes */
SQLBindCol (hstmt, 1, SQL_C_CHAR, MsgidTab, sizeof (MsgidTab[0]), MsgidIndTab);
SQLBindCol (hstmt, 2, SQL_C_CHAR, FilsdeTab, sizeof (FilsdeTab[0]), FilsdeIndTab);
SQLBindCol (hstmt, 3, SQL_C_CHAR, SubjectTab, sizeof (SubjectTab[0]), SubjectIndTab);
while ((retour = SQLFetchScroll (hstmt, SQL_FETCH_NEXT, 0)) != SQL_NO_DATA)
{
if (retour == SQL_ERROR)
deconnexion_forcee_suite_erreur_sql (hstmt, "SQLFetchScroll (): Erreur");
for (i = 0; i < NbEchantillonsParPasse; i++)
{
/* test sur le sujet seul car il n'est pas possible d'avoir msgid ou filsde absent */
if (SubjectIndTab[i] == SQL_NULL_DATA)
{
/* traitement de cette possibilité */
}
/* traitement des données */
}
}
}
Bien sur le SQLHSTMT a été déclaré, défini, alloué et paramétré auparavant.
SQL_ATTR_ROW_ARRAY_SIZE constitue la taille de l'échantillon ramené par passe de
SQLFetchScroll ()
SQL_ATTR_ROW_BIND_TYPE est défini suivant le type de paramétrage désiré: par colonne ou par
enregistrement. Ici on paramètre par colonne d'où la valeur prédéfinie SQL_BIND_BY_COLUMN
SQL_ATTR_ROWS_FETCHED_PTR paramètre un pointeur qui récupèrera le nombre d'enregistrements
effectivement ramenés lors d'une passe.
Ensuite on alloue les tampons à la requête avec SQLBindCol () (voir aussi: 8.6).
Vient alors la boucle principale qui utilise SQLFetchScroll (). La fonction utilisera le
paramétrage précédent pour récupérer TAILLE_ECHANTILLON enregistrements à chaque fois,
jusqu'à renvoyer SQL_NO_DATA qui marquera la fin de la récupération des données. Dans cette
application il m'est suffisant de tester la validité du champ filsde, avec la valeur contenue dans
le tableau donné en dernier argument à SQLBindCol (), et de traiter la donnée en conséquence.
Il est préférable de tester également la valeur contenue dans le tableau de statut, pour sauter cet
enregistrement, ou le traiter différemment, en fonction de la valeur pointée
(SQL_ROW_SUCCESS ou SQL_ROW_SUCCESS_WITH_INFO si tout va bien, SQL_ROW_ERROR
sinon).
Rien n'est montré ici concernant le traitement d'une requête ne ramenant pas de résultat car c'est le non traitement des données qui m'en assure.
L'avantage de ce dispositif est qu'il permet de traiter les données par blocs. On peut donc agir sur
un seul bloc, pour l'afficher et permettre la navigation dans le bloc par exemple.
Remarque: Il aurait fallu vérifier la valeur du SQL_ROW_STATUS_PTR, un pointeur vers
un tableau de SQLUSMALLINT, d'après la doc Microsoft MSDN, avant de tester
SubjectIndTab[i]. Ce pointeur se déclare ainsi:
SQLUSMALLINT RowStatusTab[TAILLE_ECHANTILLON]; SQLSetStmtAttr (hstmt, SQL_ATTR_ROW_STATUS_PTR, RowStatusTab, 0);Mais, hélas, je n'ai pas réussi à me servir de ce pointeur de statut, qui n'a jamais été rempli avec ce code.
SQLExecDirect () ou bien un SQLPrepare () suivi d'un SQLExecute ()
contenant une chaîne de requête, on fait ces appels avec une chaine
"CALL nomProcédure (paramètre1, paramètre2,...)". Puis on appelle SQLBindParam () et
SQLBindCol () comme d'ordinaire.
Par exemple:
SQLLEN fromInd, subjectInd, bodyInd;
SQLLEN inputInd;
if (SQLPrepare (stmt, "CALL pc_message(?)", SQL_NTS) != SQL_SUCCESS)
{
deconnexion_forcee_suite_erreur_sql (IDConnexion, stmt, "\nSQLPrepare (): Erreur");
return;
}
/* parametrage du SQLPrepare () */
if (SQLBindParam (stmt, 1, SQL_C_CHAR, SQL_CHAR, MSGID_COL_LEN, 0,
{
Msgid, &inputInd) != SQL_SUCCESS)
deconnexion_forcee_suite_erreur_sql (IDConnexion, stmt, "\nSQLBindParam (): Erreur");
return;
}
/* parametrage colonnes */
SQLBindCol (stmt, 1, SQL_C_CHAR, from, sizeof (from), &fromInd);
SQLBindCol (stmt, 2, SQL_C_CHAR, subject, sizeof (subject), &subjectInd);
SQLBindCol (stmt, 3, SQL_C_CHAR, body, sizeof (body), &bodyInd);
Le curseur étant ouvert par la procédure on peut faire le SQLFetch () ou bien
SQLFetchScroll() ensuite.
jean-michel OLTRA 2004-07-06