31.13. Système d'événements

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 fournie 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 vivantes. De plus, il existe une 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.

31.13.1. Types d'événements

La variable PGEventId de type enum précise tous les types d'événements gérés par le système d'événements. Toutes ces 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évenement 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ée 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 pertes 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é. Le procédure d'événement ne doit pas tenter un PQclear sur l'objet résultat lui-même. 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ées 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 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. C'est de la responsabilité de l'événement de nettoyer proprement les données de l'événement car libpq n'a pas cette capacité en matière de gestion de 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.

31.13.2. Procédure de rappel de l'événement

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.

[Attention]

Attention

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 est 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 qui fonctionnera 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.

31.13.3. Fonctions de support des événements

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'évenement doit être enregistré une fois pour chaque PGconn pour lequel vous souhaitez recevoir des événements. Il n'existe pas de limites, 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'évenement dans les messages d'erreur. Cette valeur ne peut pas être NULL ou une chaîne de longueur nulle. La chaîne du nom est copiée dans PGconn, donc ce qui est passé n'a pas besoin de durer longtemps. Le pointeur passThrough est passé à proc à chaque arrivée d'un événement. Cet argument peut être NULL.

PQsetInstanceData

Initialise instanceData de la connexion pour la procédure proc avec data. 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 PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
      
PQinstanceData

Renvoie le instanceData de la connexion associée avec connproc ou NULL s'il n'y en a pas.

        void *PQinstanceData(const PGconn *conn, PGEventProc proc);
      
PQresultSetInstanceData

Initialise le instanceData du résultat pour la procédure proc avec data. 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);
      
PQresultInstanceData

Renvoie le instanceData du résultat associé avec proc ou NULL s'il n'y en a pas.

        void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
      

31.13.4. Exemple d'un événement

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écssaire 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");

    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 */
}