27.4. Traitement des commandes asynchrones

La fonction PQexec est parfaite pour soumettre des commandes aux applications standards, synchrones. Néanmoins, elle a quelques déficiences qui peuvent être importantes pour certaines utilisateurs :

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

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 sinon (dans ce 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 être appelée de nouveau (sur la même connexion) tant que PQgetResult n'a pas renvoyé un pointeur nul, indiquant que la commande est terminée.

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 de la chaîne de requête. Les paramètres de la fonction sont gérés de façon identique à PQexecParams. Comme PQexecParams, PQsendQueryParams ne fonctionne pas pour les connexions utilisant le protocole 2.0 et ne permet qu'une seule commande dans la chaîne de requête.

PQsendQueryPrepared

Envoie une requête pour exécuter une instruction préparée, avec les 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);

Cette fonction est similaire à PQsendQueryParams mais la commande à exécuter est spécifiée en nommant une instruction précédemment préparée 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. Comme PQexecPrepared, PQsendQueryPrepared ne fonctionne pas pour les connexions utilisant le protocole 2.0.

PQgetResult

Attend le prochain résultat d'un appel précédent à PQsendQuery, PQsendQueryParams ou PQsendQueryPrepared, et le renvoie. Un pointeur nul 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ée de façon répétée jusqu'à ce qu'elle retourne un pointeur nul indiquant que la commande s'est terminée. (Si elle est appelée à un moment où aucune commande n'est active, PQgetResult renvoie un pointeur nul dès la première fois.) Chaque résultat non nul provenant de PQgetResult doit ê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 une fois que vous en avez terminé. Notez que PQgetResult bloque seulement si la commande est active et que les données nécessaires en réponse n'ont pas encore été lues par PQconsumeInput.

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 forme simple 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.) Néanmoins, appeler PQgetResult cause 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 une entrée est disponible à partir du serveur, elle est consommée.

int PQconsumeInput(PGconn *conn);

PQconsumeInput renvoie normalement 1 ce qui indique << aucune erreur >>, mais renvoie zéro s'il y a eu une erreur (auquel cas PQerrorMessage peut être consultée). Notez que le résultat ne dit pas si des données ont été récupérées en entrées. Après avoir appelé PQconsumeInput, l'application doit 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é à gérer un résultat ou une notification. La fonction lit les données disponibles et les sauvegarde dans un tampon, ce qui remet à zéro l'indication qu'une lecture de select() est disponible. L'application peut donc utiliser PQconsumeInput pour effacer la condition select() immédiatement, puis examiner les résultats plus tard à sa guise.

PQisBusy

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

int PQisBusy(PGconn *conn);

PQisBusy ne tente pas elle-même de lire les données à partir du serveur ; du coup, PQconsumeInput doit être appelée d'abord, sans quoi l'état occupé ne s'arrêtera jamais.

Une application typique de l'utilisation des ces fonctions a une boucle principale utilisant select() ou poll() pour attendre toutes les conditions auxquelles elle doit répondre. Une des conditions est que des données sont disponibles à partir du serveur, ce qui signifie pour select() des données lisibles sur le descripteur de fichier identifié par PQsocket. Lorsque la boucle principale détecte la disponibilité de données, elle doit appeler PQconsumeInput pour lire l'entête. Elle peut ensuite appeler PQisBusy puis PQgetResult si PQisBusy renvoie false (0). Elle peut aussi appeler PQnotifies pour détecter les messages NOTIFY (voir Section 27.6).

Un client qui utilise PQsendQuery/PQgetResult peut aussi tenter d'annuler une commande en cours de traitement par le serveur.

PQrequestCancel

Demande au serveur d'abandonner le traitement de la commande en cours.

int PQrequestCancel(PGconn *conn);

Le code de retour est 1 si la demande d'annulation a été correctement envoyée et 0 sinon. (Si non, PQerrorMessage dira pourquoi.) Un envoi correct ne garantit pas que la demande aura un effet. Quel que soit le code de retour de PQrequestCancel, l'application doit continuer la séquence normale de lecture/résultat en utilisant PQgetResult. Si l'annulation est réelle, la commande en cours se termine rapidement et renvoie un résultat d'erreur. Si l'annulation échoue (par exemple parce que le serveur a déjà terminé l'exécution de la commande), aucun résultat n'est visible.

Notez que si la commande en cours fait partie d'un bloc de transaction, l'annulation est effective pour la transaction complète.

PQrequestCancel peut être appelée en toute sécurité depuis un gestionnaire de signaux. Donc, il est aussi possible de l'utiliser avec des PQexec, si la décision d'annuler doit être faite dans un gestionnaire de signaux. Par exemple, psql appelle PQrequestCancel à partir du gestionnaire du signal SIGINT, autorisant du coup l'annulation interactive des commandes qu'il envoie via PQexec.

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. (C'est bien plus probable si l'application envoie des données via COPY IN.) Pour empêcher cette possibilité et réussir 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 à PQsendQuery, PQputline, PQputnbytes, et PQendcopy ne bloquent pas mais renvoient à la place une erreur s'ils ont besoin d'être de nouveau appelés.

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

PQisnonblocking

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

int PQisnonblocking(const PGconn *conn);

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

PQflush

Tente de vider les données en attente d'envoi vers le 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 si elle n'a pas encore pu envoyer 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. Si elle renvoie 1, attendez que la socket soit disponible en écriture et appelez-la de nouveau ; répétez cela jusqu'à ce qu'elle renvoie 0. Une fois que PQflush renvoie 0, attendez que la socket soit disponible en lecture puis lisez la réponse comme décrit ci-dessus.