Le système d'événements de libpq est conçu pour
notifier les gestionnaires d'événements enregistrés de l'arrivée d'événements
intéressants de la libpq, comme par exemple la
création ou la destruction d'objets PGconn
et
PGresult
. Un cas d'utilisation principal est de
permettre aux applications d'associer leur propres données avec un
PGconn
ou un PGresult
et
de s'assurer que les données soient libérées au bon moment.
Chaque gestionnaire d'événement enregistré est associé avec deux types de
données, connus par libpq comme des pointeurs
opaques, c'est-à-dire void *
. Il existe un pointeur
passthrough fourni par l'application quand le
gestionnaire d'événements est enregistré avec un PGconn
.
Le pointeur passthrough ne change jamais pendant toute la durée du
PGconn
et des PGresult
générés grâce à lui ; donc s'il est utilisé, il doit pointer vers
des données à longue vie. De plus, il existe un pointeur de données
instanciées, qui commence à NULL dans chaque objet
PGconn
et PGresult
. Ce
pointeur peut être manipulé en utilisant les fonctions
PQinstanceData
,
PQsetInstanceData
,
PQresultInstanceData
et
PQsetResultInstanceData
. Notez que, contrairement au
pointeur passthrough, les PGresult
n'héritent pas
automatiquement des données instanciées d'un
PGconn
. libpq ne sait
pas vers quoi pointent les pointeurs passthrough et de données instanciées,
et n'essaiera hamais de les libérer -- cela tient de la responsabilité
du gestionnaire d'événements.
L'enum PGEventId
précise tous les types
d'événements gérés par le système d'événements. Toutes ses valeurs ont des
noms commençant avec PGEVT
. Pour chaque type d'événement,
il existe une structure d'informations sur l'événement, précisant les
paramètres passés aux gestionnaires d'événement. Les types d'événements
sont :
PGEVT_REGISTER
L'événement d'enregistrement survient quand PQregisterEventProc
est appelé. C'est le moment idéal pour initialiser toute structure
instanceData
qu'une procédure d'événement pourrait avoir
besoin. Seul un événement d'enregistrement sera déclenché par gestionnaire
d'événement sur une connexion. Si la procédure échoue, l'enregistrement
est annulé.
typedef struct { PGconn *conn; } PGEventRegister;
Quand un événement PGEVT_REGISTER
est reçu, le pointeur
evtInfo
doit être converti en un
PGEventRegister *
. Cette structure contient un
PGconn
qui doit être dans le statut
CONNECTION_OK
; garanti si
PQregisterEventProc
est appelé juste après avoir
obtenu un bon PGconn
. Lorsqu'elle renvoit
un code d'erreur, le nettoyage doit être réalisé car aucun événement
PGEVT_CONNDESTROY
ne sera envoyé.
PGEVT_CONNRESET
L'événement de réinitialisation de connexion est déclenché après un
PQreset
ou un PQresetPoll
. Dans
les deux cas, l'événement est seulement déclenché si la ré-initialisation
est réussie. Si la procédure échoue, la réinitialisation de connexion
échouera ; la structure PGconn
est placée
dans le statut CONNECTION_BAD
et
PQresetPoll
renverra
PGRES_POLLING_FAILED
.
typedef struct { PGconn *conn; } PGEventConnReset;
Quand un événement PGEVT_CONNRESET
est reçu, le
pointeur evtInfo
doit être converti en un
PGEventConnReset *
. Bien que le
PGconn
a été réinitialisé, toutes les données
de l'événement restent inchangées. Cet événement doit être utilisé pour
ré-initialiser/recharger/re-requêter tout instanceData
associé. Notez que même si la procédure d'événement échoue à traiter
PGEVT_CONNRESET
, elle recevra toujours un événement
PGEVT_CONNDESTROY
à la fermeture de la connexion.
PGEVT_CONNDESTROY
L'événement de destruction de la connexion est déclenché en réponse
à PQfinish
. Il est de la responsabilité de la
procédure de l'événement de nettoyer proprement ses données car
libpq n'a pas les moyens de gérer cette mémoire. Un échec du
nettoyage amènera des fuites de mémoire.
typedef struct { PGconn *conn; } PGEventConnDestroy;
Quand un événement PGEVT_CONNDESTROY
est reçu, le
pointeur evtInfo
doit être converti en un
PGEventConnDestroy *
. Cet événement est
déclenché avant que PQfinish
ne réalise d'autres
nettoyages. La valeur de retour de la procédure est ignorée car il
n'y a aucun moyen d'indiquer un échec de PQfinish
.
De plus, un échec de la procédure ne doit pas annuler le nettoyage de
la mémoire non désirée.
PGEVT_RESULTCREATE
L'événement de création de résultat est déclenché en réponse à
l'utilisation d'une fonction d'exécution d'une requête, par exemple
PQgetResult
. Cet événement sera déclenché seulement
après la création réussie du résultat.
typedef struct { PGconn *conn; PGresult *result; } PGEventResultCreate;
Quand un événement PGEVT_RESULTCREATE
est reçu, le
pointeur evtInfo
doit être converti en un
PGEventResultCreate *
. Le paramètre
conn
est la connexion utilisée pour générer le
résultat. C'est le moment idéal pour initialiser tout
instanceData
qui doit être associé avec le résultat.
Si la procédure échoue, le résultat sera effacé et l'échec sera propagé.
La procédure d'événement ne doit pas tenter elle-même un
PQclear
sur l'objet résultat.
Lors du renvoi d'un code d'échec, tout le
nettoyage doit être fait car aucun événement
PGEVT_RESULTDESTROY
ne sera envoyé.
PGEVT_RESULTCOPY
L'événement de copie du résultat est déclenché en réponse à un
PQcopyResult
. Cet événement se déclenchera
seulement une fois la copie terminée. Seules les procédures qui ont
géré avec succès l'événement PGEVT_RESULTCREATE
ou PGEVT_RESULTCOPY
pour le résultat source recevront
les événements PGEVT_RESULTCOPY
.
typedef struct { const PGresult *src; PGresult *dest; } PGEventResultCopy;
Quand un événement PGEVT_RESULTCOPY
est reçu, le
pointeur evtInfo
doit être converti en un
PGEventResultCopy *
. Le résultat
src
correspond à ce qui a été copié
alors que le résultat dest
correspond à la
destination. Cet événement peut être utilisé pour fournir une copie
complète de instanceData
, ce que
PQcopyResult
ne peut pas faire. Si la procédure
échoue, l'opération complète de copie échouera et le résultat
dest
sera effacé. Au renvoi d'un code d'échec,
tout le nettoyage doit être réalisé car aucun événement
PGEVT_RESULTDESTROY
ne sera envoyé pour le résultat
de destination.
PGEVT_RESULTDESTROY
L'événement de destruction de résultat est déclenché en réponse à la
fonction PQclear
. Il est de la responsabilité de
l'événement de nettoyer proprement les données de l'événement car libpq
n'a pas la capacité de gérer cette mémoire. Si le nettoyage
échoue, cela sera la cause de pertes mémoire.
typedef struct { PGresult *result; } PGEventResultDestroy;
Quand un événement PGEVT_RESULTDESTROY
est reçu, le
pointeur evtInfo
doit être converti en un
PGEventResultDestroy *
. Cet événement est
déclenché avant que PQclear
ne puisse faire de
nettoyage. La valeur de retour de la procédure est ignorée car il
n'existe aucun moyen d'indiquer un échec à partir de
PQclear
. De plus, un échec de la procédure ne doit
pas annuler le nettoyage de la mémoire non désirée.
PGEventProc
PGEventProc
est une définition de type pour un pointeur
vers une procédure d'événement, c'est-à-dire la fonction utilisateur
appelée pour les événements de la libpq. La signature d'une telle fonction
doit être :
int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
Le paramètre evtId
indique l'événement
PGEVT
qui est survenu. Le pointeur
evtInfo
doit être converti vers le type
de structure approprié pour obtenir plus d'informations sur l'événement.
Le paramètre passThrough
est le pointeur fourni
à PQregisterEventProc
quand la procédure de
l'événement a été enregistrée. La fonction doit renvoyer une valeur
différente de zéro en cas de succès et zéro en cas d'échec.
Une procédure d'événement particulière peut être enregistrée une fois
seulement pour un PGconn
. Ceci est dû au fait
que l'adresse de la procédure est utilisée comme clé de recherche pour
identifier les données instanciées associées.
Sur Windows, les fonctions peuvent avoir deux adresses différentes :
une visible de l'extérieur de la DLL et une visible de l'intérieur. Il
faut faire attention que seule une de ces adresses soit utilisée avec les
fonctions d'événement de la libpq, sinon une
confusion en résultera. La règle la plus simple pour écrire du code
fonctionnel est de s'assurer que les procédures d'événements sont
déclarées static
. Si l'adresse de la procédure doit
être disponible en dehors de son propre fichier source, il faut exposer
une fonction séparée pour renvoyer l'adresse.
PQregisterEventProc
Enregistre une procédure de rappel pour les événements avec libpq.
int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough);
Une procédure d'événement doit être enregistrée une fois pour chaque
PGconn
pour lequel vous souhaitez recevoir des
événements. Il n'existe pas de limite, autre que la mémoire, sur le
nombre de procédures d'événements qui peuvent être enregistrées avec
une connexion. La fonction renvoie une valeur différente de zéro en cas
de succès, et zéro en cas d'échec.
L'argument proc
sera appelé quand se déclenchera
un événement libpq. Son adresse mémoire est aussi utilisée pour rechercher
instanceData
. L'argument name
est utilisé pour faire référence à la procédure d'événement dans les
messages d'erreur. Cette valeur ne peut pas être NULL
ou une chaîne de
longueur nulle. La chaîne name
est copiée dans
PGconn
, donc ce qui est passé n'a pas besoin
d'exister longtemps. Le pointeur passThrough
est
passé à proc
à chaque arrivée d'un événement. Cet
argument peut être NULL
.
PQsetInstanceData
Initialise avec data
l'instanceData
de la connexion
conn
pour la
procédure proc
.
Cette fonction renvoit zéro en cas d'échec et autre chose en cas de réussite.
(L'échec est seulement possible si proc
n'a pas
été correctement enregistré dans conn
.)
int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
PQinstanceData
Renvoie l'instanceData
de la connexion
conn
associée
au proc
ou NULL
s'il
n'y en a pas.
void *PQinstanceData(const PGconn *conn, PGEventProc proc);
PQresultSetInstanceData
Initialise avec data
l'instanceData
du résultat pour la
procédure proc
.
Cette fonction renvoit zéro en cas d'échec et autre chose en cas de réussite.
(L'échec est seulement possible si proc
n'a pas
été correctement enregistré dans le résultat.)
int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
Attention, aucun stockage représenté par data
ne
sera pris en compte par PQresultMemorySize
, à moins
qu'il est alloué en utilisant PQresultAlloc
.
(Faire ceci est recommandé car cela élimine le besoin de libérer le stockage explicitement
quand le résultat est supprimé.)
PQresultInstanceData
Renvoie l'instanceData
du résultat associé à
proc
ou NULL
s'il n'y
en a pas.
void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
Voici un exemple d'une gestion de données privées associée aux connexions et aux résultats de la libpq.
/* en-tête nécessaire pour les événements de la libpq (note : inclut libpq-fe.h) */ #include <libpq-events.h> /* la donnée instanciée : instanceData */ typedef struct { int n; char *str; } mydata; /* PGEventProc */ static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough); int main(void) { mydata *data; PGresult *res; PGconn *conn = PQconnectdb("dbname=postgres options=-csearch_path="); if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); PQfinish(conn); return 1; } /* appelée une fois pour toute connexion qui doit recevoir des événements. * Envoit un PGEVT_REGISTER à myEventProc. */ if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL)) { fprintf(stderr, "Cannot register PGEventProc\n"); PQfinish(conn); return 1; } /* la connexion instanceData est disponible */ data = PQinstanceData(conn, myEventProc); /* Envoit un PGEVT_RESULTCREATE à myEventProc */ res = PQexec(conn, "SELECT 1 + 1"); /* le résultat instanceData est disponible */ data = PQresultInstanceData(res, myEventProc); /* Si PG_COPYRES_EVENTS est utilisé, envoit un PGEVT_RESULTCOPY à myEventProc */ res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS); /* le résultat instanceData est disponible si PG_COPYRES_EVENTS a été * utilisé lors de l'appel à PQcopyResult. */ data = PQresultInstanceData(res_copy, myEventProc); /* Les deux fonctions de nettoyage envoient PGEVT_RESULTDESTROY à myEventProc */ PQclear(res); PQclear(res_copy); /* Envoit un PGEVT_CONNDESTROY à myEventProc */ PQfinish(conn); return 0; } static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) { switch (evtId) { case PGEVT_REGISTER: { PGEventRegister *e = (PGEventRegister *)evtInfo; mydata *data = get_mydata(e->conn); /* associe des données spécifiques de l'application avec la connexion */ PQsetInstanceData(e->conn, myEventProc, data); break; } case PGEVT_CONNRESET: { PGEventConnReset *e = (PGEventConnReset *)evtInfo; mydata *data = PQinstanceData(e->conn, myEventProc); if (data) memset(data, 0, sizeof(mydata)); break; } case PGEVT_CONNDESTROY: { PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo; mydata *data = PQinstanceData(e->conn, myEventProc); /* libère les données instanciées car la connexion est en cours de destruction */ if (data) free_mydata(data); break; } case PGEVT_RESULTCREATE: { PGEventResultCreate *e = (PGEventResultCreate *)evtInfo; mydata *conn_data = PQinstanceData(e->conn, myEventProc); mydata *res_data = dup_mydata(conn_data); /* associe des données spécifiques à l'application avec les résultats (copié de la connexion) */ PQsetResultInstanceData(e->result, myEventProc, res_data); break; } case PGEVT_RESULTCOPY: { PGEventResultCopy *e = (PGEventResultCopy *)evtInfo; mydata *src_data = PQresultInstanceData(e->src, myEventProc); mydata *dest_data = dup_mydata(src_data); /* associe des données spécifiques à l'application avec les résultats (copié d'un résultat) */ PQsetResultInstanceData(e->dest, myEventProc, dest_data); break; } case PGEVT_RESULTDESTROY: { PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo; mydata *data = PQresultInstanceData(e->result, myEventProc); /* libère les données instanciées car le résultat est en cours de destruction */ if (data) free_mydata(data); break; } /* unknown event id, just return TRUE. */ default: break; } return TRUE; /* event processing succeeded */ }