Documentation PostgreSQL 14.22 » Interfaces client » libpq -- Bibliothèque C » Traitement des commandes asynchrones

Précédent | Niveau supérieur | Suivant
34.3. Fonctions d'exécution de commandes | Sommaire | 34.5. Mode pipeline

## 34.4. Traitement des commandes asynchrones

La fonction `PQexec` est adéquate pour soumettre des commandes aux applications standards, synchrones. Néanmoins, elle a quelques défauts pouvant être d'importance à certains utilisateurs :

- `PQexec` attend que la commande se termine. L'application pourrait avoir du travail ailleurs (comme le rafraîchissement de l'interface utilisateur), auquel cas elle ne voudra pas être bloquée en attente de la réponse.

- Comme l'exécution de l'application cliente est suspendue en attendant le résultat, il est difficile pour l'application de décider qu'elle voudrait annuler la commande en cours (c'est possible avec un gestionnaire de signaux mais pas autrement).

- `PQexec` ne peut renvoyer qu'une structure `PGresult`. Si la chaîne de commande soumise contient plusieurs commandes SQL, toutes les structures `PGresult` sont annulées par `PQexec`, sauf la dernière.

- `PQexec` récupère toujours le résultat entier de la commande, le mettant en cache dans une seule structure `PGresult`. Bien que cela simplifie la logique de la gestion des erreurs pour l'application, cela peut ne pas se révéler pratique pour les résultats contenant de nombreuses lignes.

Les applications qui n'apprécient pas ces limitations peuvent utiliser à la place les fonctions sous-jacentes à partir desquelles `PQexec` est construit : `PQsendQuery` et `PQgetResult`. Il existe aussi `PQsendQueryParams`, `PQsendPrepare`, `PQsendQueryPrepared`, `PQsendDescribePrepared` et `PQsendDescribePortal`, pouvant être utilisées avec `PQgetResult` pour dupliquer les fonctionnalités de respectivement `PQexecParams`, `PQprepare` , `PQexecPrepared`, `PQdescribePrepared` et `PQdescribePortal`.

`PQsendQuery`

  Soumet une commande au serveur sans attendre le(s) résultat(s). 1 est renvoyé si la commande a été correctement envoyée et 0 dans le cas contraire (auquel cas, utilisez la fonction `PQerrorMessage` pour obtenir plus d'informations sur l'échec).

      int PQsendQuery(PGconn *conn, const char *command);

  Après un appel réussi à `PQsendQuery`, appelez `PQgetResult` une ou plusieurs fois pour obtenir les résultats. `PQsendQuery` ne peut pas être appelé de nouveau (sur la même connexion) tant que `PQgetResult` ne renvoie pas de pointeur NULL, indiquant que la commande a terminé.

  Dans le mode pipeline, les chaînes de commande contenant plus d'une commande SQL sont interdites.

`PQsendQueryParams`

  Soumet une commande et des paramètres séparés au serveur sans attendre le(s) résultat(s).

      int PQsendQueryParams(PGconn *conn,
                                   const char *command,
                                   int nParams,
                                   const Oid *paramTypes,
                                   const char * const *paramValues,
                                   const int *paramLengths,
                                   const int *paramFormats,
                                   int resultFormat);

  Ceci est équivalent à `PQsendQuery` sauf que les paramètres de requêtes peuvent être spécifiés séparément à partir de la chaîne de requête. Les paramètres de la fonction sont gérés de façon identique à `PQexecParams`.

`PQsendPrepare`

  Envoie une requête pour créer une instruction préparée avec les paramètres donnés et redonne la main sans attendre la fin de son exécution.

            int PQsendPrepare(PGconn *conn,
               const char *stmtName,
               const char *query,
               int nParams,
               const Oid *paramTypes);

  Ceci est la version asynchrone de `PQprepare` : elle renvoie 1 si elle a été capable d'envoyer la requête, 0 sinon. Après un appel terminé avec succès, appelez `PQgetResult` pour déterminer si le serveur a créé avec succès l'instruction préparée. Les paramètres de la fonction sont gérés de façon identique à `PQprepare` .

`PQsendQueryPrepared`

  Envoie une requête pour exécuter une instruction préparée avec des paramètres donnés sans attendre le(s) résultat(s).

      int PQsendQueryPrepared(PGconn *conn,
                                     const char *stmtName,
                                     int nParams,
                                     const char * const *paramValues,
                                     const int *paramLengths,
                                     const int *paramFormats,
                                     int resultFormat);

  Ceci est similaire à `PQsendQueryParams` mais la commande à exécuter est spécifiée en nommant une instruction préparée précédente au lieu de donner une chaîne contenant la requête. Les paramètres de la fonction sont gérés de façon identique à `PQexecPrepared`.

`PQsendDescribePrepared`

  Soumet une requête pour obtenir des informations sur l'instruction préparée indiquée sans attendre sa fin.

              int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

  Ceci est la version asynchrone de `PQdescribePrepared` : elle renvoie 1 si elle a été capable d'envoyer la requête, 0 dans le cas contraire. Après un appel réussi, appelez `PQgetResult` pour obtenir les résultats. Les paramètres de la fonction sont gérés de façon identique à `PQdescribePrepared`.

`PQsendDescribePortal`

  Soumet une requête pour obtenir des informations sur le portail indiqué sans attendre la fin de la commande.

              int PQsendDescribePortal(PGconn *conn, const char *portalName);

  Ceci est la version asynchrone de `PQdescribePortal` : elle renvoie 1 si elle a été capable d'envoyer la requête, 0 dans le cas contraire. Après un appel réussi, appelez `PQgetResult` pour obtenir les résultats. Les paramètres de la fonction sont gérés de façon identique à `PQdescribePortal`.

`PQgetResult`

  Attend le prochain résultat d'un appel précédent à `PQsendQuery`, `PQsendQueryParams`, `PQsendPrepare`, `PQsendQueryPrepared`, `PQsendDescribePrepared`, `PQsendDescribePortal`, ou `PQpipelineSync`, et le renvoie. Un pointeur NULL est renvoyé quand la commande est terminée et qu'il n'y aura plus de résultats.

      PGresult *PQgetResult(PGconn *conn);

  `PQgetResult` doit être appelé de façon répétée jusqu'à ce qu'il retourne un pointeur NULL indiquant que la commande s'est terminée. (Si appelée à un moment où aucune commande n'est active, `PQgetResult` renverra juste immédiatement un pointeur NULL). Chaque résultat non NULL provenant de `PQgetResult` devrait être traité en utilisant les mêmes fonctions d'accès à `PGresult` que celles précédemment décrites. N'oubliez pas de libérer chaque objet résultat avec `PQclear` quand vous en avez terminé. Notez que `PQgetResult` bloquera seulement si la commande est active et que les données nécessaires en réponse n'ont pas encore été lues par `PQconsumeInput`.

  Dans le mode pipeline, `PQgetResult` retournera normalement jusqu'à l'arrivée d'une erreur ; pour les requêtes suivantes envoyées après celle qui a causé l'erreur jusqu'au point suivant de synchronisation (ce dernier exclus), un résultat spécial de type `PGRES_PIPELINE_ABORTED` sera renvoyé, et un pointeur nul sera renvoyé après cela. Quand le pointde synchronisation du pipeline est atteint, un résultat de type `PGRES_PIPELINE_SYNC` sera renvoyé. Le résultat de la requête suivant le point de synchronisation suit immédiatement (autrement dit aucun pointeur nul n'est renvoyé après le point de synchronisation).

  ### Note

  Même quand `PQresultStatus` indique une erreur fatale, `PQgetResult` doit être appelé jusqu'à ce qu'il renvoie un pointeur NULL pour permettre à libpq de traiter l'information sur l'erreur correctement.

Utiliser `PQsendQuery` et `PQgetResult` résout un des problèmes de `PQexec` : si une chaîne de commande contient plusieurs commandes SQL, les résultats de ces commandes peuvent être obtenus individuellement (ceci permet une simple forme de traitement en parallèle : le client peut gérer les résultats d'une commande alors que le serveur travaille sur d'autres requêtes de la même chaîne de commandes).

Une autre fonctionnalité fréquemment demandée, pouvant être obtenue avec `PQsendQuery` et `PQgetResult` est la récupération d'un gros résultat une ligne à la fois. Ceci est discuté dans Section 34.6.

Néanmoins, appeler `PQgetResult` causera toujours un blocage du client jusqu'à la fin de la prochaine commande SQL. Ceci est évitable en utilisant proprement deux fonctions supplémentaires :

`PQconsumeInput`

  Si l'entrée est disponible à partir du serveur, la consomme.

      int PQconsumeInput(PGconn *conn);

  `PQconsumeInput` renvoie normalement 1 indiquant « aucune erreur », mais renvoie zéro s'il y a eu une erreur (auquel cas `PQerrorMessage` peut être consulté). Notez que le résultat ne dit pas si des données ont été récupérées en entrée. Après avoir appelé `PQconsumeInput`, l'application devrait vérifier `PQisBusy` et/ou `PQnotifies` pour voir si leur état a changé.

  `PQconsumeInput` peut être appelée même si l'application n'est pas encore préparée à traiter un résultat ou une notification. La fonction lira les données disponibles et les sauvegardera dans un tampon indiquant ainsi qu'une lecture d'un `select()` est possible. L'application peut donc utiliser `PQconsumeInput` pour effacer la condition `select()` immédiatement, puis examiner les résultats à loisir.

`PQisBusy`

  Renvoie 1 si une commande est occupée, c'est-à-dire que `PQgetResult` bloque en attendant une entrée. Un zéro indique que `PQgetResult` peut être appelé avec l'assurance de ne pas bloquer.

      int PQisBusy(PGconn *conn);

  `PQisBusy` ne tentera pas lui-même de lire les données à partir du serveur ; du coup, `PQconsumeInput` doit être appelé d'abord ou l'état occupé ne prendra jamais fin.

Une application typique de l'utilisation de ces fonctions aura une boucle principale utilisant `select()` ou `poll()` pour attendre que toutes les conditions soient remplies. Une des conditions sera la disponibilité des données à partir du serveur, ce qui signifie des données lisibles pour `select()` sur le descripteur de fichier identifié par `PQsocket`. Lorsque la boucle principale détecte la disponibilité de données, elle devrait appeler `PQconsumeInput` pour lire l'en-tête. Elle peut ensuite appeler `PQisBusy` suivi par `PQgetResult` si `PQisBusy` renvoie false (0). Elle peut aussi appeler `PQnotifies` pour détecter les messages `NOTIFY` (voir la Section 34.9).

Un client qui utilise `PQsendQuery`/`PQgetResult` peut aussi tenter d'annuler une commande en cours de traitement par le serveur ; voir la Section 34.7. Mais quelle que soit la valeur renvoyée par `PQcancel` , l'application doit continuer avec la séquence normale de lecture du résultat en utilisant `PQgetResult`. Une annulation réussie causera simplement une fin plus rapide de la commande.

En utilisant les fonctions décrites ci-dessus, il est possible d'éviter le blocage pendant l'attente de données du serveur. Néanmoins, il est toujours possible que l'application se bloque en attendant l'envoi vers le serveur. C'est relativement peu fréquent mais cela peut arriver si de très longues commandes SQL ou données sont envoyées (mais c'est bien plus probable si l'application envoie des données via `COPY IN`). Pour éviter cette possibilité et parvenir à des opérations de bases de données totalement non bloquantes, les fonctions supplémentaires suivantes peuvent être utilisées.

`PQsetnonblocking`

  Initialise le statut non bloquant de la connexion.

      int PQsetnonblocking(PGconn *conn, int arg);

  Initialise l'état de la connexion à non bloquant si `arg` vaut 1 et à bloquant si `arg` vaut 0. Renvoie 0 si OK, -1 en cas d'erreur.

  Dans l'état non bloquant, les appels réussis à `PQsendQuery`, `PQputline`, `PQputnbytes`, `PQputCopyData` et `PQendcopy` ne bloqueront pas ; leurs modifications seront stockées dans un tampon local de sortie jusqu'à ce qu'elles soient vidées sur disque. Les appels échoués renverront une erreur et devront être tentés de nouveau.

  Notez que `PQexec` n'honore pas le mode non bloquant ; s'il est appelé, il agira d'une façon bloquante malgré tout.

`PQisnonblocking`

  Renvoie le statut bloquant de la connexion à la base de données.

      int PQisnonblocking(const PGconn *conn);

  Renvoie 1 si la connexion est en mode non bloquant, 1 dans le cas contraire.

`PQflush`

  Tente de vider les données des queues de sortie du serveur. Renvoie 0 en cas de succès (ou si la queue d'envoi est vide), -1 en cas d'échec quelle que soit la raison ou 1 s'il a été incapable d'envoyer encore toutes les données dans la queue d'envoi (ce cas arrive seulement si la connexion est non bloquante).

      int PQflush(PGconn *conn);

Après avoir envoyé une commande ou des données dans une connexion non bloquante, appelez `PQflush`. S'il renvoie 1, attendez que la socket devienne prête en lecture ou en écriture. Si elle est prête en écriture, appelez de nouveau `PQflush`. Si elle est prête en lecture, appelez `PQconsumeInput`, puis appelez `PQflush`. Répétez jusqu'à ce que `PQflush` renvoie 0. (Il est nécessaire de vérifier si elle est prête en lecture, et de vidanger l'entrée avec `PQconsumeInput` car le serveur peut bloquer en essayant d'envoyer des données, par exemple des messages NOTICE, et ne va pas lire nos données tant que nous n'avons pas lu les siennes.) Une fois que `PQflush` renvoie 0, attendez que la socket soit disponible en lecture, puis lisez la réponse comme décrit ci-dessus.