IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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

  • 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 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, PQsendQueryPrepared, PQsendDescribePrepared ou PQsendDescribePortal, 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.

[Note]

Note

Même quand PQresultStatus indique une erreur fatale, PQgetResult doit être appelé jusqu'à ce qu'il renvoie un pointeur nul 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 obtenu avec PQsendQuery et PQgetResult est la récupération d'un gros résultat une ligne à la fois. Ceci est discuté dans Section 31.5, « Récupérer le résultats des requêtes ligne par ligne ».

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 31.8, « 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 31.6, « 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.