Documentation PostgreSQL 14.22 » Interfaces client » libpq -- Bibliothèque C » Système d'événements

Précédent | Niveau supérieur | Suivant
34.13. Traitement des messages | Sommaire | 34.15. Variables d'environnement

## 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 `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.

### 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, 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 renvoie 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.

### 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 renvoie 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 renvoie 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) */
                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 */
    }