PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 16.4 » Interfaces client » libpq -- Bibliothèque C » Système d'événements

34.14. 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 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 PQresultSetInstanceData. 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.

34.14.1. Types 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 (renvoie zéro), 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. La valeur de retour de la procédure événement est ignoré dans les versions 15 et ultérieures de PostgreSQL. Avec les versions précédentes, il est important de renvoyer le succès (différent de zéro), sinon la connexion sera annulée.

        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 une procédure d'événement échoue (renvoie zéro), cet événement de procédure sera ignoré pour la durée de vie restante du résultat ; autrement dit, il ne recevra pas les événements PGEVT_RESULTCOPY ou PGEVT_RESULTDESTROY pour ce résultat ou les résultats copiés à partir de lui.

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 une procédure d'événement échoue (renvoie zéro), cet événement de procédure sera ignoré pour la durée de vie restante du résultat ; autrement dit, il ne recevra pas les événements PGEVT_RESULTCOPY ou PGEVT_RESULTDESTROY pour ce résultat ou les résultats copiés à partir de lui.

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.

34.14.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

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.

34.14.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'é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);
       

34.14.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é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)
    {
        /* PQerrorMessage's result includes a trailing newline */
        fprintf(stderr, "%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) */
            PQresultSetInstanceData(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) */
            PQresultSetInstanceData(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 */
}