29.4. Traitement des commandes asynchrones
La fonction PQexec est adéquate pour
soumettre des commandes aux applications standards, synchrones.
Néanmoins, il a quelques déficiences pouvant être d'importance à
certains utilisateurs :
-
PQexec attend que la commande se
termine. L'application pourrait avoir d'autres travaux à
réaliser (comme le rafraichissement de l'interface
utilisateur), auquel cas il ne voudra pas être bloqué 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.
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 pourrait être appelé de nouveau
(sur la même connexion) jusqu'à ce que PQgetResult renvoie un pointeur nul, indiquant
que la commande a terminé.
-
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 à
partir de la chaîne de requête. Les paramètres de la fonction
sont gérés de façon identique à PQexecParams. Comme PQexecParams, cela ne fonctionnera pas pour les
connexions utilisant le protocole 2.0 et cela ne permettra
qu'une seule commande dans la chaîne de requête.
-
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. Comme
PQprepare, cela ne fonctionnera pas
sur les connexions utilisant le protocole 2.0.
-
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é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, cela ne fonctionnera pas pour
les connexions utilisant le protocole 2.0.
-
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. Comme PQdescribePrepared, cela ne fontionnera pas
avec les connexions utilisant le protocole 2.0.
-
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. Comme PQdescribePortal, cela ne fontionnera pas avec
les connexions utilisant le protocole 2.0.
-
PQgetResult
-
Attend le prochain résultat d'un appel précédant à PQsendQuery, PQsendQueryParams, PQsendPrepare 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é de façon
répété jusqu'à ce qu'il retourne un pointeur nul indiquant que
la commande s'est terminée (si appelé à un moment où aucune
commande n'est active, PQgetResult
renverra seulement un pointeur nul à la fois). Chaque résultat
non nul 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 une fois que
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.
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). 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, consommez-la.
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 pourrait être appelé
même si l'application n'est pas encore préparé à gérer 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 pour
examiner les résultats autant que possible.
-
PQisBusy
-
Renvoie 1 si une commande est occupée, c'est-à-dire que
PQgetResult bloquerait en attendant
une entrée. Un zéro indiquerait que PQgetResult peut être appelé avec l'assurance
de ne pas être bloqué.
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 s'arrêtera jamais.
Une application typique de l'utilisation de ces fonctions aura une
boucle principale utilisant select() ou
poll() pour attendre toutes les conditions
auxquelles il doit répondre. 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, il devrait appeler
PQconsumeInput pour lire l'en-tête. Il peut
ensuite appeler PQisBusy suivi par
PQgetResult si PQisBusy renvoie false (0). Il peut aussi appeler
PQnotifies pour détecter les messages
NOTIFY
(voir la
Section 29.7,
« Notification asynchrone »).
Un client qui utilise PQsendQuery/PQgetResult
peut aussi tenter d'annuler une commande en cours de traitement par
le serveur ; voir la Section 29.5,
« Annuler des requêtes en cours d'exécution ». Mais
quelque 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 (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 pourraient ê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 bloqueront pas mais renverront à
la place une erreur s'ils ont besoin d'être de nouveau appelés.
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 quelque 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 soit disponible en écriture et appelez-la de
nouveau ; répétez cela jusqu'à ce qu'il 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.
|