PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 15BETA2 » Internes » Protocole Frontend/Backend » Flot de messages

54.2. Flot de messages

Cette section décrit le flot de message et la sémantique de chaque type de message. (Les détails sur la représentation exacte de chaque message apparaît dans Section 54.7.) Il existe plusieurs sous-protocoles différents dépendant de l'état de la connexion : démarrage, requête, appel de fonction, COPY, et fin. Il existe aussi plusieurs cas spéciaux pour les opérations asynchrones (incluant les réponses aux notifications et les annulations de commande), qui peuvent survenir à tout moment après la phase de démarrage.

54.2.1. Démarrage (Start-up)

Pour commencer une session, un client ouvre une connexion au serveur et envoie un message de démarrage. Ce message inclut les noms de l'utilisateur et de la base de données sur laquelle l'utilisateur souhaite se connecter ; il identifie aussi la version particulière du protocole à utiliser. (En option, le message de démarrage peut inclure différentes configurations de paramètres.) Le serveur utilise ensuite cette information et le contenu des fichiers de configuration (tels que pg_hba.conf) pour déterminer si la connexion est acceptable et le type d'authentification requis (si c'est le cas).

Ensuite, le serveur envoie un message approprié de demande d'authentification, auquel le client doit répondre avec un message approprié de réponse d'authentification (par exemple, avec un mot de passe). Pour toutes les méthodes d'authentification, sauf GSSAPI, SSPI et SASL, il existe au moins une requête et une réponse. Pour certaines méthodes, aucune réponse n'est nécessaire, et donc aucune demande d'authentification n'arrive. Pour les méthodes GSSAPI, SSPI et SASL, plusieurs échanges de paquets pourraient être nécessaires pour terminer l'authentification.

Le cycle d'authentification se termine avec le serveur rejetant la tentative de connexion (message ErrorResponse), ou renvoyant le message AuthenticationOk.

Dans cette phase, les messages possibles provenant du serveur sont :

ErrorResponse

La demande de connexion a été rejetée. Le serveur ferme immédiatement la connexion.

AuthenticationOk

L'échange d'authentification s'est terminé avec succès.

AuthenticationKerberosV5

Le client doit maintenant prendre part à un dialogue d'authentification Kerberos V5 (qui n'est pas décrit ici, et fait partie de la spécification Kerberos) avec le serveur. Si ce dialogue réussit, le serveur répond avec un AuthenticationOk, sinon il répond avec un ErrorResponse. Ceci n'est plus accepté.

AuthenticationCleartextPassword

Le client doit maintenant envoyer un message PasswordMessage contenant le mot de passe en clair. Si le mot de passe est bon, le serveur répond avec un message AuthenticationOk, sinon il répond avec un message ErrorResponse.

AuthenticationMD5Password

Le client doit maintenant envoyer un message PasswordMessage contenant le mot de passe (avec le nom de l'utilisateur) chiffré avec MD5, puis chiffré de nouveau en utilisant le sel aléatoire de quatre octets indiqué dans le message AuthenticationMD5Password. S'il s'agit du bon mot de passe, le serveur répond avec un message AuthenticationOk, sinon il répond avec nu message ErrorResponse. Le message PasswordMessage réel peut être calculé en SQL avec un concat('md5', md5(concat(md5(concat(motdepasse, nomdutilisateur)), selaléatoire))). (Gardez en tête que la fonction md5() renvoie le résultat sous la forme d'une chaîne hexadécimale.)

AuthenticationSCMCredential

Cette réponse est uniquement possible pour les connexions locales de type domaine Unix sur les plateformes qui acceptent les messages d'identification SCM. Le client doit envoyer un message d'identification SCM, puis envoyer un seul octet de données. (Le contenu de l'octet de données est inintéressant ; il est seulement utilisé pour s'assurer que le serveur attend suffisamment longtemps pour recevoir le message d'identification.) Si l'identification est acceptable, le serveur répond avec un message AuthenticationOk, sinon il répond avec un message ErrorResponse. (Ce type de message est seulement lancé par les serveurs dont la version est antérieure à la version 9.1. Cela pourrait éventuellement être supprimé à partir de la spécification du protocole.)

AuthenticationGSS

Le client doit maintenant initier une négociation GSSAPI. Le client enverra un message GSSResponse avec la première partie du flux de données GSSAPI en réponse à cela. Si plusieurs messages sont nécessaires, le serveur répondra avec AuthenticationGSSContinue.

AuthenticationSSPI

Le client doit maintenant initier une négociation SSPI. Le client enverra un message GSSResponse avec la première partie du flux de données SSPI en réponse à cela. Si plusieurs messages sont nécessaires, le serveur répondra avec AuthenticationGSSContinue.

AuthenticationGSSContinue

Ce message contient les données de réponse de l'étape précédente de la négociation GSSAPI ou SSPI (AuthenticationGSS, AuthenticationSSPI ou un AuthenticationGSSContinue précédent). Si les données GSSAPI ou SSPI de ce message indique que plus de données sont nécessaires pour terminer l'authentification, le client doit envoyer les données dans un autre message GSSResponse. Si l'authentification GSSAPI ou SSPI est terminée par ce message, le serveur enverra ensuite un message AuthenticationOk pour indiquer une authentification réussie ou un message ErrorResponse pour indiquer un échec.

AuthenticationSASL

Le client doit maintenant initier une négociation SASL, en utilisant un des mécanismes SASL listés dans le message. Le client doit envoyer un message SASLInitialResponse avec le nom du mécanisme sélectionné, et la première partie du flux de données SASL en réponse à ceci. Si plus de messages sont nécessaires, le serveur répondra avec un message AuthenticationSASLContinue. Voir Section 54.3 pour les détails.

AuthenticationSASLContinue

Ce message contient des données de challenge provenant des étapes précédentes de la négociation SASL (AuthenticationSASL, ou d'un précédent AuthenticationSASLContinue). Le client doit répondre avec un message SASLResponse.

AuthenticationSASLFinal

L'authentification SASL a terminé avec les données supplémentaires spécifiques du mécanisme pour le client. Le serveur enverra ensuite le message AuthenticationOk pour indiquer une authentification réussie ou un message ErrorResponse pour indiquer un échec. Ce message est envoyé uniquement si le mécanisme SASL indique que des données supplémentaires doivent être envoyées du serveur au client à la fin.

NegotiateProtocolVersion

Le serveur n'accepte pas la version mineure du protocole réclamée par le client, mais accepte une version plus ancienne du protocole ; ce message indique la version mineure la plus haute que le serveur accepté. Ce message sera aussi envoyé si le client a demandé des options non acceptées du protocole (par exemple commençant avec _pq_.) dans le paquet de démarrage. Ce message sera suivi par un message ErrorResponse ou par un message indiquant le succès ou l'échec de l'authentification.

Si le client n'accepte pas la méthode d'authentification demandée par le client, il doit immédiatement fermer la connexion.

Après avoir reçu le message AuthenticationOk, le client doit attendre plus de messages du serveur. Dans cette phase, un processus backend est démarré, et le client est juste un partenaire intéressé. Il est toujours possible que la demande de démarrage échoue (ErrorResponse) ou que le serveur décline le support de la version mineure demandée du protocole (NegotiateProtocolVersion), mais dans un cas normal, le backend enverra quelques messages ParameterStatus, BackendKeyData et enfin ReadyForQuery.

Lors de cette phase, le backend tentera d'appliquer toute configuration supplémentaire de paramètre d'exécution donnée lors du message de démarrage. EN cas de succès, ces valeurs deviennent les valeurs par défaut de la session. Une erreur cause un message ErrorResponse puis quitte.

Les messages possibles provenant du backend dans cette phase sont :

BackendKeyData

Ce message fournit des données de clé secrète que le client doit conserver s'il souhaite être capable d'annuler des requêtes plus tard. Le client ne doit pas répondre à ce message mais doit continuer à attendre un message ReadyForQuery.

ParameterStatus

Ce message informe le client sur la configuration actuelle (initiale) d'un paramètre serveur, tel que client_encoding ou datestyle. Le client peut ignorer ce message, ou enregistrer la configuration pour une utilisation ultérieure ; voir Section 54.2.6 pour plus de détails. Le client ne doit pas répondre à ce message, mais doit continuer à attendre un message ReadyForQuery.

ReadyForQuery

Le démarrage est terminé. Le client peut maintenant exécuter des commandes.

ErrorResponse

Le démarrage a échoué. La connexion est fermée après l'envoi de ce message.

NoticeResponse

Un message d'avertissement a été envoyé. Le client doit afficher le message mais continuer à attendre des messages ReadyForQuery ou ErrorResponse.

Le message ReadyForQuery est le même que le backend enverra après chaque cycle de commande. Suivant les besoins du client, il est raisonnable de considérer le message ReadyForQuery comme débutant un cycle de commande ou de le considérer comme terminant la phase de démarrage et chaque cycle de commandes suivant.

54.2.2. Requête simple

Un cycle de requête simple est initié par le client envoyant un message Query au backend. The message inclut une commande SQL (ou des commandes) exprimée sous la forme d'une chaîne de caractères. Le backend envoie alors une ou plusieurs réponses suivant le contenu de la chaîne de texte, et termine par un message ReadyForQuery. Ce dernier message informe le client qu'il peut envoyer une nouvelle commande. (Il n'est pas absolument nécessaire que le client attende le message ReadyForQuery avant de lancer une autre commande, mais le client prend alors la responsabilité de comprendre ce qu'il se passe si la commande précédente échoue et que les commandes suivantes déjà lancées réussissent.)

Les messages de réponse possibles du backend sont :

CommandComplete

Une commande SQL terminée normalement.

CopyInResponse

Le backend est prêt à copier des données du client vers une table ; voir Section 54.2.5.

CopyOutResponse

Le backend est prêt à copier les données d'une table vers le client ; voir Section 54.2.5.

RowDescription

Indique que les lignes sont prêtes à être renvoyées en réponse à une requête SELECT, FETCH, etc. Le contenu de ce message décrit la disposition des colonnes pour les lignes. Ceci sera suivi par un message DataRow pour chaque ligne renvoyée au client.

DataRow

Un des ensembles de lignes renvoyés par une requête SELECT, FETCH, etc.

EmptyQueryResponse

Une chaîne a été reconnue à la place d'une requête.

ErrorResponse

Une erreur est survenue.

ReadyForQuery

Le traitement de la requête est terminé. Un message séparé est envoyé pour l'indiquer parce que la chaîne de texte pourrait contenir plusieurs commandes SQL. (Le message CommandComplete marque la fin du traitement d'une commande SQL, et non pas de la chaîne complète.) Le message ReadyForQuery sera toujours envoyé, que le traitement se termine avec succès ou avec échec.

NoticeResponse

Un message d'avertissement a été envoyé en relation à la requête. Les avertissements sont en plus des autres réponses, autrement dit le backend continuera de traiter la commande.

La réponse à une requête SELECT (ou autres requêtes qui renvoient des ensembles de lignes, tels que EXPLAIN ou SHOW), consiste normalement en des messages RowDescription, zéro ou plusieurs messages DataRow, et enfin un message CommandComplete. COPY vers ou à partir du client fait appel à un protocole spécial décrit dans Section 54.2.5. Tous les autres types de requêtes produisent normalement seulement un message CommandComplete.

Comme une chaîne de texte peut contenir plusieurs requêtes (séparées par des points-virgules), il pourrait y avoir plusieurs séquences de réponses avant que le backend finisse le traitement de la chaîne. Le message ReadyForQuery est lancé quand la chaîne entière a été traitée et que le backend est prêt à accepter une nouvelle chaîne de texte.

Si une chaîne de caractères complètement vide est reçue (aucun contenu autre que les espaces blancs), la réponse est EmptyQueryResponse suivi par ReadyForQuery.

Au cas où une erreur survient, le message ErrorResponse est envoyé suivi d'un message ReadyForQuery. Tous les autres traitement de chaîne de texte sont annulés par le message ErrorResponse (même s'il reste des requêtes dans la chaîne). Notez que ceci pourrait survenir au milieu de la séquence de messages générés par une requête individuel.

Dans le mode simple requête, le format de valeurs récupérées est toujours du texte, sauf quand la commande données est un FETCH à partir d'un curseur déclaré avec l'option BINARY. Dans ce cas, les valeurs récupérées sont dans un format binaire. Les codes format donnés dans le message RowDescription indiquent le format utilisé.

Un client doit être préparé pour accepter des messages ErrorResponse et NoticeResponse à chaque fois qu'il est attendu d'autres types de message. Voir aussi Section 54.2.6 pour les messages que le backend pourrait générer à cause d'événements externes.

Une pratique recommandée est de coder des clients dans un style machine d'états qui acceptera tout type de message à tout moment où cela aurait du sens, plutôt que de coder des suppositions sur la séquence exacte des messages.

54.2.2.1. Plusieurs requêtes dans le mode requête simple

Quand un message simple Query contient plus d'une requête SQL (séparé par des points-virgules), ces requêtes sont exécutées comme une seule transaction, sauf si des commandes de contrôle de transaction explicites sont inclus pour forcer un comportement différent. Par exemple, si le message contient :

INSERT INTO mytable VALUES(1);
SELECT 1/0;
INSERT INTO mytable VALUES(2);

alors une erreur de division par zéro dans la requête SELECT forcera l'annulation du premier INSERT. De plus, comme l'exécution du message est abandonnée à la première erreur, le deuxième INSERT n'est jamais tenté.

Si le message contient à la place :

BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELECT 1/0;

alors le premier INSERT est validé par la commande COMMIT explicite. Le deuxième INSERT et le SELECT sont toujours traités comme une seule transaction, donc l'échec de division par zéro annulera le second INSERT, mais pas le premier.

Ce comportement est implémenté en exécutant les requêtes dans un message message multi-requêtes dans un bloc de transaction implicite sauf s'il existe un bloc de transaction explicite pour leur exécution. La principale différence entre un bloc de transaction implicite et un bloc standard est qu'un bloc implicite est fermé automatiquement à la fin d'un message Query, soit par une validation implicite s'il n'y a pas d'erreur, soit par une annulation implicite s'il y avait une erreur. Ceci est similaire à la validation ou à l'annulation implicite qui survient pour une requête exécutée par elle-même (quand elle n'est pas dans un bloc de transaction).

Si la session est déjà dans un bloc de transaction, en résultat d'un BEGIN dans un message précédent, alors le message Query continue simplement ce bloc de transaction, si le message contient une ou plusieurs requêtes. Néanmoins, si le message Query contient un COMMIT ou un ROLLBACK fermant le bloc de transaction existant, alors toutes les requêtes suivantes sont exécutées dans un bloc de transaction explicite. Inversement, si un BEGIN apparaît dans un message Query multi-requêtes, alors il démarre un bloc de transaction standard qui sera uniquement terminé par un COMMIT ou ROLLBACK explicite, apparaissant soit dans ce message Query soit dans un message suivant. Si le BEGIN suit certaines requêtes qui ont été exécutées sous la forme d'un bloc de transaction implicite, ces requêtes ne sont pas immédiatement validées ; en effet, elles sont inclues rétroactivement dans le nouveau bloc de transaction standard.

Un COMMIT ou un ROLLBACK apparaissant dans un bloc de transaction implicite est exécuté de façon normal, fermant le bloc implicite ; néanmoins, un message d'avertissement sera renvoyé car un COMMIT ou un ROLLBACK sans BEGIN pourrait être une erreur. Si des requêtes suivent, un nouveau bloc de transaction implicite sera démarré pour elles.

Les savepoints ne sont pas autorisés dans un bloc de transaction implicite car elles pourraient entrer en conflit avec le comportement de fermeture automatique du bloc en cas d'erreur.

Rappelez-vous que, quelque soient les commandes de contrôle des transactions présentes, l'exécution d'un message Query s'arrête à la première erreur. Donc sur cet exemple :

BEGIN;
SELECT 1/0;
ROLLBACK;

dans un seul message Query, la session sera laissée à l'intérieur d'un bloc de transaction standard en échec car ROLLBACK n'est pas atteint après l'erreur de division par zéro. Un autre ROLLBACK sera nécessaire pour restaurer la session dans un état utilisable.

Un autre comportement à noter est que l'analyse lexicale et syntaxique est réalisée sur la chaîne de caractères entière avant qu'une seule requête ne soit exécutée. De ce fait, les erreurs simples (telle qu'un mot clé mal orthographié) dans des requêtes ultérieures peuvent empêcher l'exécution des requêtes. Ceci est habituellement invisible pour les utilisateurs car les requêtes vont de toute façon être intégralement annulées quand elles font partie d'un bloc de transaction implicite. Cependant, cela peut se voir lors d'une tentative sur plusieurs transactions dans un message Query multi-requêtes. Par exemple, si une faute est intégrée à l'exemple précédent comme ceci :

BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELCT 1/0;

alors aucune des requêtes ne sera exécutée, résultant en une différence visible, à savoir que le premier INSERT n'est pas validé. Les erreurs détectées lors de l'analyse sémantique ou plus tard, comme une table ou une colonne mal nommée, n'ont pas cet effet.

54.2.3. Requête étendue

Le protocole de requête étendue divise le protocole de requête simple décrit ci-dessus en plusieurs étapes. Le résultat des étapes préparatoires peut être ré-utilisé plusieurs fois pour améliorer l'efficacité. De plus, des fonctionnalités supplémentaires sont disponibles, tel que la possibilité de fournir des valeurs de données comme paramètres séparés au lieu d'avoir à les insérer directement dans la chaîne de la requête.

Dans le protocole étendu, le client envoie en premier lieu un message Parse, qui contient une chaîne de caractères pour la requête, avec en options quelques informations sur les types de données des paramètres, et le nom d'un objet représentant la requête préparée (une chaîne vide indiquera une requête préparée sans nom). La réponse est soit un message ParseComplete soit un message ErrorResponse. Les types de données des paramètres peuvent être des types de données indiquées par leur OID. Si aucun type n'est indiqué, l'analyseur tentera de deviner les types de données de la même façon qu'il le ferait pour des constantes de chaînes non typées.

Note

Un type de paramètre peut être laissé sans spécification en le configurant à zéro ou en faisant en sorte que le table d'OID des types de paramètres soit de taille inférieur au nombre de symboles de paramètres ($n) utilisés dans la chaîne de requête. Un autre cas spécial est qu'un type de paramètre peut être indiqué comme void (autrement dit, l'OID du pseudo-type void). Ceci permet l'utilisation de symboles de paramètre pour les paramètres de fonction qui sont en réalité des paramètres OUT. D'habitude, il n'y a pas de contexte dans lequel un paramètre void pourrait être utilisé, mais si un tel symbole de paramètre apparaît dans la liste de paramètres d'une fonction, il est en fait ignoré. Par exemple, un appel de fonction tel que foo($1,$2,$3,$4) pourrait correspondre à une fonction avec deux arguments IN et deux arguments OUT, si $3 et $4 sont indiqués comme étant de type void.

Note

La chaîne de requête contenue dans un message Parse ne peut pas inclure plus d'une requête SQL ; dans le cas contraire une erreur SQL est levée. Cette restriction n'existe pas dans le protocole requête simple mais elle existe dans le protocole étendu parce que permettre à des requêtes préparées ou à des portails de contenir plusieurs commandes compliquerait indument le protocole.

En cas de succès à la création, un objet nommé de requête préparé existe jusqu'à la fin de la session, sauf s'il est détruit explicitement. Une requête préparée sans nom dure seulement jusqu'à l'exécution du prochain message Parse pour une requête préparée sans nom. (Notez qu'un message Query détruit aussi une requête préparée sans nom.) Les requêtes préparées nommées doivent être explicitement fermées avant de pouvoir être redéfinies par un autre message Parse, mais ceci n'est pas requis pour une requête préparée sans nom. Les requêtes préparées nommées peuvent aussi être créées et accédées au niveau des commandes SQL en utilisant les instructions PREPARE et EXECUTE.

Une fois qu'une requête préparée existe, elle peut être préparée pour une exécution en utilisant le message Bind. Ce dernier donne le nom de la requête préparée source (ou une chaîne vide dans le cas d'une requête préparée sans nom) et les valeurs à utiliser pour tous les paramètres présents dans la requête préparée. L'ensemble de paramètres fournis doit correspondre ceux requis par la requête préparée. (Si vous avez indiqué un ou plusieurs paramètres void dans le message Parse, donnez des valeurs NULL pour chacune dans le message Bind.) Bind spécifie aussi le format à utiliser pour toute donnée renvoyée par la requête ; le format peut être spécifié de façon globale ou par colonne. La réponse est soit BindComplete soit ErrorResponse.

Note

Le choix entre une sortie texte et une sortie binaire est déterminé par les codes format donnés dans Bind, quelque soit la commande SQL impliquée. L'attribut BINARY dans les déclarations de curseur est hors sujet lors de l'utilisation du protocole de requête étendue.

La planification/optimisation de la requête survient typiquement quand le message Bind est traité. Si la requête préparée n'a pas de paramètres ou si elle est exécutée de façon répétée, le serveur pourrait sauvegarde le plan créé et le ré-utiliser lors des messages Bind suivants pour la même requête préparée. Néanmoins, il le fera seulement s'il trouve qu'un plan générique peut être créé, sans être trop inefficace par rapport à un plan dépendant des valeurs spécifiques fournies pour les paramètres. Ceci survient de façon transparente pour ce qui concerne le protocole.

S'il est créé avec succès, un objet portail nommé dure jusqu'à la fin de la transaction en cours, sauf en cas de destruction explicite. Un portail non nommé est détruit à la fin de la transaction ou dès l'exécution du prochain message Bind pour un portal non nommé. (Notez qu'un simple message Query détruit aussi le portail sans nom.) Les portails nommés doivent être fermés explicitement avant de pouvoir être redéfinis par un autre message Bind, mais ceci n'est pas requis pour un portail non nommé. Les portails nommés peuvent aussi être utilisés et accédés au niveau des commandes SQL, en utilisant les instructions DECLARE CURSOR et FETCH.

Une fois que le portail existe, il peut être exécuté en utilisant un message Execute. Ce dernier spécifie le nom du portail (une chaîne vide indique un portail non nommé) et un nombre maximum de lignes de résultat (zéro signifiant « récupère toutes les lignes »). Ce nombre a seulement un sens pour les portails contenant des commandes renvoyant des ensembles de lignes ; dans les autres cas, la commande est toujours exécutée jusqu'à sa fin, et le nombre de lignes est ignoré. Les réponses possibles à Exécute sont les mêmes que celles décrites ci-dessus pour les requêtes lancées via le protocole de requête simple, sauf que Execute ne cause pas l'exécution de ReadyForQuery ou RowDescription.

Si Execute termine avant la fin de l'exécution d'un portail (à cause de l'atteinte d'un nombre de lignes résultats différent de zéro), il enverra un message PortalSuspended ; l'apparence de ce message indique au client qu'un autre Execute devrait être exécuté contre le même portail pour terminer l'opération. Le message CommandComplete indiquant la fin de la commande SQL source n'est pas envoyé jusqu'à la fin de l'exécution du portail. De ce fait, une phase Execute est toujours terminée par l'apparition d'exactement un de ces messages : CommandComplete, EmptyQueryResponse (si le portail a été créé à partir d'une chaîne vide de requête), ErrorResponse ou PortalSuspended.

À la fin de chaque série de messages du protocole de requête étendue, le client doit envoyer un message Sync. Ce message sans paramètre fait que le backend ferme la transaction en cours si elle n'est pas à l'intérieur d'un bloc de transaction BEGIN/COMMIT (« ferm » signifiant une validation s'il n'y a pas d'erreur et une annulation dans le cas d'une erreur). Une réponse ReadyForQuery est ensuite produite. Le but de Sync est de fournir un point de resynchronisation pour les erreurs. Quand une erreur est détectée lors du traitement de tout message du protocole de requête étendu, le backend envoie un message ErrorResponse, puis lit et annule les messages jusqu'à la réception d'un Sync, envoie un message ReadyForQuery et enfin retourne à un traitement habituelle des messages. (Mais notez qu'une erreur dans le traitement du message Sync n'est pas ignoré -- ceci assure qu'il y a bien un et un seul ReadyForQuery envoyé pour chaque Sync.)

Note

Sync ne ferme pas un bloc de transaction ouvert avec BEGIN. Il est possible de détecter cette situation car le message ReadyForQuery inclut des informations de statut de la transaction.

En plus de ces opérations fondamentales et requises, il existe plusieurs opérations optionnelles pouvant être utilisées avec le protocole de requête étendue.

Le message Describe (variant du portail) indique le nom d'un portail existant (ou une chaîne vide pour le portail non nommé). La réponse est un message RowDescription décrivant les lignes qui seront retournées par l'exécution du portail ; ou un message NoData si le portail ne contient pas une requête qui renverra des lignes ; ou ErrorResponse s'il n'existe pas un tel portail.

Le message Describe (variant de la requête) indique le nom d'une requête préparée existante (ou une chaîne vide pour la requête préparée non nommée). La réponse est un message ParameterDescription décrivant les paramètres nécessaires pour la requête, suivi par un message RowDescription décrivant les lignes qui seront renvoyés quand la requête sera enfin exécutée (ou un message NoData si la requête ne renverra pas de lignes). Le message ErrorResponse est renvoyé si cette requête préparée n'existe pas. Notez que, comme Bind n'a pas été exécutée, les formats à utiliser pour les colonnes renvoyés ne sont pas encore connus du backend ; les champs de code format seront à zéro dans le message RowDescription dans ce cas.

Astuce

Dans la plupart des scénarios, le client doit envoyer une variante ou l'autre de Describe avant d'envoyer le message Execute, pour s'assurer qu'il sait comment interpréter les résultats qu'il récupérera.

Le message Close ferme une requête préparée ou un portail existant, et libère les ressources. Ce n'est pas une erreur d'envoyer Close pour un nom inexistant de requête préparée ou de portail. La réponse est habituellement CloseComplete, mais pourrait être ErrorResponse si des difficultés sont rencontrés lors de la libération des ressources. Notez que fermer implicitement une requête préparée ferme tout portail ouvert qui était construit par cette requête.

Le message Flush ne cause pas la génération d'une sortie spécifique mais force le backend à renvoyer toute donnée en attente dans les buffers de sortie. Un Flush doit être envoyé après toute commande de requête étendue sauf Sync, si le client souhaite examiner les résultats de cette commande avant de lancer d'autres commandes. Sans Flush, les messages renvoyés par le backend seront combinés dans le plus petit nombre de paquets pour minimiser la surcharge réseau.

Note

Le message Query en requête simple est approximativement équivalent à une série Parse, Bind, Describe portail, Execute, Close, Sync, en utilisant une requête préparée et un portail non nommés et aucun paramètre. Une différence est qu'il acceptera plusieurs requêtes SQL dans la chaîne de requête, réalisant automatiquement les séquences bind/describe/execute pour chaque requête, les unes à la suite des autres. Une autre différence est qu'il ne renvoie pas les messages ParseComplete, BindComplete, CloseComplete et NoData.

54.2.4. Appel de fonction (Function Call)

Le sous-protocole d'appel de fonction permet au client de demander un appel direct de toute fonction qui existe dans le catalogue système pg_proc de la base de données. Le client doit avoir le droit d'exécution sur la fonction.

Note

Le sous-protocole d'appel de fonction est une ancienne fonctionnalité qu'il est certainement préférable d'éviter dans du nouveau code. Des résultats similaires peuvent être accomplis en configurant une requête préparée qui exécute SELECT function($1, ...). Le cycle de l'appel de fonction peut ensuite être remplacé avec les messages Bind/Execute.

Un cycle d'appel de fonction est initié par le client en envoyant un message FunctionCall au backend. Le backend envoie alors un ou plusieurs messages de réponse suivant le résultat de l'appel de fonction, et termine avec un message ReadyForQuery. Ce message informe le client qu'il peut envoyer une nouvelle requête ou un nouvel appel de fonction.

Les messages de réponse possible provenant du backend sont :

ErrorResponse

Une erreur est survenue

FunctionCallResponse

L'appel de fonction s'est terminé et a renvoyé le résultat donné dans le message. (Notez que le protocole d'appel de fonction peut seulement gérer un résultat scalaire simple, pas un type ligne ou un ensemble de résultats.)

ReadyForQuery

Le traitement de l'appel de fonction est terminé. Le message ReadyForQuery sera toujours envoyé, que le traitement termine avec succès ou avec une erreur.

NoticeResponse

Un message d'avertissement a été lancé en relation avec l'appel de fonction. Les notes sont en plus des autres réponses, autrement dit le backend continuera à traiter la commande.

54.2.5. Opérations COPY

La commande COPY permet une transfert de données en masse très rapide du ou à partir du serveur. Les opérations de copie vers le serveur (copy-in) ou à partir du serveur (copy-out) font basculer la connexion dans un sous-protocole distinct, qui dure jusqu'à la fin de l'opération.

Le mode Copy-in (transfert de données vers le serveur) est initié quand le backend exécute une requête SQL COPY FROM STDIN. Le backend envoie un message CopyInResponse au client. Le client doit alors envoyer zéro ou plusieurs messages CopyData, formant un flux de données en entrée. (Les limites du message ne sont pas nécessaires liées aux limites des lignes, bien qu'il s'agit souvent d'un choix raisonnable.) Le client peut terminer le mode copy-in en envoyant soit un message CopyDone (permettant une fin avec succès) ou un message CopyFail (qui causera l'échec de la requête SQL COPY avec un message d'erreur). Le backend annule alors le mode de traitement de commande dans lequel il était entré au début de la commande COPY, qui sera soit le protocole de requête simple ou celui de requête étendu. Il enverra ensuite soit un message CommandComplete (en cas de réussite) soit un message ErrorResponse (dans le cas contraire).

Dans le cas d'une erreur détecté par le backend lors du mode copy-in (incluant la réception d'un message CopyFail), le backend enverra un message ErrorResponse. Si la commande COPY a été lancée via un message de requête étendue, le backend ignorera maintenant les messages du client jusqu'à la réception d'un message Sync. Après, il enverra un message ReadyForQuery et retournera à un traitement normal. Si la commande COPY a été lancé via un message Query, le reste de ce message est ignoré et le message ReadyForQuery est envoyé. Dans tous les cas, les messages CopyData, CopyDone ou CopyFail suivants envoyés par le client seront simplement ignorés.

Le backend ignorera les messages Flush et Sync reçus lors du mode copy-in. La réception de tout autre message qui ne concerne pas la copie constitue une erreur qui annulera l'état copy-in comme décrit ci-dessus. (L'exception pour Flush et Sync est pour simplifier les bibliothèques clientes qui envoient toujours Flush ou Sync après un message Execute, sans vérifier si la commande à exécuter est un COPY FROM STDIN.)

Le mode Copy-out (transfert de données à partir du serveur) est initié lorsque le backend exécute une requête SQL COPY TO STDOUT. Le backend envoie un message CopyOutResponse au client, suivi par zéro ou plusieurs messages CopyData (toujours un par ligne), suivi par un message CopyDone. Le backend annulera alors le mode de traitement de commande dans lequel il était avant le lancement de COPY. Enfin, il envoie CommandComplete. Le client ne peut pas annuler le transfert (sauf en fermant la connexion ou en envoyant une requête Cancel), mais il peut ignorer les messages CopyData et CopyDone indésirables.

Dans le cas d'une erreur détectée par le backend en mode copy-out, le backend lancera un message ErrorResponse et reviendra en traitement normal. Le client devrait traiter la réception de ErrorResponse comme terminant le mode copy-out.

Il est possible que les messages NoticeResponse et ParameterStatus soient mélangés entre des messages CopyData ; les clients doivent gérer ces cas, et devraient être préparés aussi pour tout type de message asynchrone (voir Section 54.2.6). Sinon, tout type de message autre que CopyData ou CopyDone peut être traité comme terminant le mode copy-out.

Il existe un autre mode relatif à la copie, appelé copy-both, qui permet des transferts de données en masse et très rapides vers et à partir du serveur. Le mode copy-both est initié quand un backend en mode walsender exécute une instruction START_REPLICATION. Le backend envoie un message CopyBothResponse au client. Le backend et le client peuvent ensuite envoyer des messages CopyData jusqu'à ce que l'un des deux envoie un message CopyDone. Une fois que le client a envoyé un message CopyDone, le connexion passe du mode copy-both au mode copy-out et le client ne peut plus envoyer de messages CopyData. De la même façon, quand le serveur envoie un message CopyDone, la connexion passe en mode copy-in, et le serveur ne peut plus envoyer de messages CopyData. Une fois que les deux ont envoyé un message CopyDone, le mode de copie est terminé, et le backend revient au mode de traitement des commandes. Dans le cas où une erreur est détectée par le backend pendant le mode copy-both, le backend enverra un message ErrorResponse, ignorera les messages du client jusqu'à ce qu'un message Sync ne soit reçu, puis enverra un message ReadyForQuery avant de revenir au traitement normal. Le client doit traiter la réception d'un message ErrorResponse comme terminant la copie dans les deux directions ; aucun message CopyDone ne devra être envoyé dans ce cas. Voir Section 54.4 pour plus d'informations sur le sous-protocole transmis sur le mode copy-both.

Les messages CopyInResponse, CopyOutResponse et CopyBothResponse incluent des champs qui informent le client du nombre de colonnes par ligne et des codes format utilisés pour chaque colonne. (Sur l'implémentation actuelle, toutes les colonnes d'une opération COPY donnée utiliseront le même format, mais le design des messages ne le force pas.)

54.2.6. Opérations asynchrones

Il existe différents cas où le backend enverra des messages qui ne sont pas spécifiquement demandés par le flux de commande du client. Les clients doivent être préparés à gérer aussi ces messages à tout moment, même quand une requête n'est pas en cours. Au minimum, un client devrait vérifier ces cas avant de continuer à lire la réponse à une requête.

Il est possible que des messages NoticeResponse soient générés à cause d'une activité externe ; par exemple, si l'administrateur de la base de données ordonne un arrêt « rapide » de la base de données, le backend enverra un message NoticeResponse indiquant ce fait avant de fermer la connexion. Les clients doivent donc être prêts à accepter et afficher les messages NoticeResponse, même quand la connexion n'exécute pas de requêtes.

Les messages ParameterStatus seront générés à chaque fois qu'une valeur change pour un des paramètres pour lesquels le backend pense que le client doit être notifié. La plupart du temps, ceci survient en réponse à une commande SQL SET exécutée par le client, et ce cas est effectivement synchrone -- mais il est aussi possible qu'un changement de valeur de paramètres survient parce que l'administrateur a modifié un fichier de configuration, puis envoyé le signal SIGHUP au serveur. De plus, si une commande SET est annulé, un message ParameterStatus approprié sera généré pour rapporter la valeur effective.

Il existe actuellement un ensemble, codé en dur, de paramètres pour lesquels un message ParameterStatus sera généré. En voici la liste : server_version, server_encoding, client_encoding, application_name, default_transaction_read_only, in_hot_standby, is_superuser, session_authorization, DateStyle, IntervalStyle, TimeZone, integer_datetimes et standard_conforming_strings. (server_encoding, TimeZone et integer_datetimes ne sont pas rapportés avant la version 8.0 ; standard_conforming_strings n'est pas rapporté avant la version 8.1 ; IntervalStyle n'est pas rapporté avant la version 8.4 ; application_name n'est pas rapporté avant la version 9.0 ; default_transaction_read_only et in_hot_standby ne sont pas rapportés avant la version 14.) Notez que server_version, server_encoding et integer_datetimes sont des pseudos-paramètres qui ne peuvent pas changer après le démarrage. Cet ensemble pourrrait être modifié dans le futur, voire même devenir configurable. En conséquence, un client devrait simplement ignorer les messages ParameterStatus pour les paramètres qu'il ne comprend pas ou qui ne sont pas intéressants pour lui.

Si un client exécute une commande LISTEN, alors le backend enverra un message NotificationResponse (à ne pas confondre avec NoticeResponse !) à chaque fois qu'une commande NOTIFY est exécutée pour le même nom de canal.

Note

Actuellement, NotificationResponse peut seulement être envoyé en dehors d'une transaction, et donc il ne surviendra pas au milieu d'une série de réponses à des commandes, bien qu'il puisse survenir juste avant un message ReadyForQuery. Il est déconseillé de concevoir la logique du client comme assumant cela. Une bonne pratique est d'être capable d'accepter un message NotificationResponse à tout moment dans le protocole.

54.2.7. Annuler des requêtes en cours

Lors du traitement d'une requête, le client pourrait demander l'annulation de la requête. La demande d'annulation n'est pas envoyée directement sur la connexion ouverte sur le backend pour des raisons d'efficacité de l'implémentation : nous ne voulons pas avoir le backend en train de vérifier constamment pour une nouvelle entrée en provenance du client lors du traitement de la requête. Les demandes d'annulation sont relativement peu fréquentes, donc nous pouvons les rendre un peu compliquées pour éviter un pénalité sur les cas normaux.

Pour demander une annulation, le client ouvre une nouvelle connexion au serveur et envoie un message CancelRequest, plutôt que le message StartupMessage qui serait habituellement envoyé lors d'une nouvelle connexion. Le serveur traitera cette demande, puis fermera la connexion. Pour des raisons de sécurité, aucune réponse directe n'est faite au message de demande d'annulation.

Un message CancelRequest sera ignoré sauf s'il contient la même donnée clé (PID et clé sécrète) passée au client lors de la connexion. Si la demande correspond au PID et à la clé secrète pour un backend en cours d'exécution d'une requête, le traitement de la requête est annulé. (Dans l'implémentation actuelle, ceci se fait en envoyant un signal spécial au processus backend qui traite la requête.)

Le signal d'annulation pourrait avoir un effet ou pas -- par exemple, s'il arrive après que le backend ait terminé le traitement de la requête, il n'aura aucun effet. Si l'annulation est réelle, la commande en cours est arrêtée avec un message d'erreur.

Le résultat de tout ceci est que, pour des raisons de sécurité et d'efficacité, le client n'a pas de façon de savoir si la requête d'annulation a réussi. Il doit continuer d'attendre que le backend réponse à la requête. Exécuter une annulation améliore simplement les chances que la requête en cours termine rapidement, et améliore les chances qu'elle échouera avec un message d'erreur au lieu d'un succès.

Comme la requête d'annulation est envoyée sur une nouvelle connexion au serveur et non pas via le lien standard de communication client/backend, il est possible d'envoyer une demande d'annulation pour tout processus, pas uniquement pour le client dont la requête est à annuler. Ceci apporte une flexibilité supplémentaire lors de la construction d'applications multi-processus. Cela introduit aussi un risque de sécurité dans le fait que des personnes non autorisées pourraient tenter d'annuler des requêtes. Le risque de sécurité est mitigé par le besoin d'une clé secrète générée dynamiquement, à fournir lors de demandes d'annulation.

54.2.8. Arrêt

La procédure normale et propre d'arrêt est que le client envoie un message Terminate et ferme immédiatement la connexion. Sur réception de ce message, le backend ferme la connexion et quitte.

Dans les rares cas (tel qu'un arrêt de base demandé par l'administrateur), le backend pourrait se déconnecter sans demande du client. Dans ce genre de cas, le backend tentera d'envoyer un message d'erreur ou d'information, donnant la raison de la déconnexion avant de fermer la connexion.

D'autres scénarios d'arrêt surgissent de différents cas d'échec, tel qu'un core dump d'un bout ou de l'autre, une perte du lien de communication, une perte de la synchronisation sur les messages, etc. Si soit le client soit le backend voit une fin inattendue de la connexion, il doit faire son ménage et quitter. Le client a l'option de lancer un nouveau backend en recontactant le serveur s'il ne veut pas quitter. Fermer la connexion est aussi conseillé si un type de message non reconnu est reçu car cela indique probablement la perte de synchronisation des messages.

Pour un arrêt normal ou anormal, toute transaction ouverte est annulée, et non pas validée. Néanmoins, notez que si un client se déconnecter alors qu'une requête autre qu'un SELECT est en cours de traitement, le backend finira probablement la requête sans se rendre compte de la déconnexion. Si la requête est exécutée en dehors d'un bloc de transaction (séquence BEGIN ... COMMIT), alors ses résultats pourraient être validées avant que le backend ne s'aperçoive de la déconnexion.

54.2.9. Chiffrement SSL de la session

Si PostgreSQL a été compilé avec le support de SSL, les communications client/backend peuvent être chiffrés en utilisant SSL. Cela fournit une sécurité sur la communication pour les environnements où des attaquants pourraient être en mesure de capturer le trafic de la session. Pour plus d'informations sur le chiffrement des sessions PostgreSQL avec SSL, voir Section 19.9.

Pour initier une connexion chiffrée avec SSL, le client envoie dès le début un message SSLRequest au lieu de StartupMessage. Le serveur répond avec un seul octet contenant S ou N, indiquant s'il est, respectivement, prêt ou non à utiliser SSL. Le client pourrait fermer la connexion à ce point s'il n'est pas satisfait par la réponse. Pour continuer après un S, le client réalise une poignée de main SSL de démarrage (non décrit ici, car partie de la spécification SSL) avec le serveur. Si la poignée de main est réussie, le client continue avec l'habituel StartupMessage. Dans ce cas, le message StartupMessage et toutes les données qui suivent seront chiffrés avec SSL. Pour continuer après un N, le client envoie le message habituel StartupMessage et continue sans chiffrement. (Il est aussi possible de lancer un message GSSENCRequest après une réponse N pour tenter l'utilisation du chiffrement GSSAPI à la place du chiffrement SSL.)

Le client doit aussi se préparer à gérer un message ErrorMessage en réponse à un message SSLRequest. Ceci ne peut survenir que si le serveur est antérieur à l'ajout du support de SSL support à PostgreSQL. (De tels serveurs sont maintenant très anciens, et ne devraient plus exister actuellement.) Dans ce cas, la connexion doit être fermée mais le client pourrait choisir d'ouvrir une connexion propre et continuer sans demander SSL.

Quand le chiffrement SSL peut être réalisé, le serveur est supposé envoyer l'octet S, puis attendre que le client initie une poignée de main SSL. Si des octets supplémentaires sont disponibles en lecture, cela signifie probablement qu'une attaque de type man-in-the-middle est en cours et tente de réaliser la technique buffer-stuffing (CVE-2021-23222). Les clients doivent être codés soit pour lire exactement un octet de la socket avant de renvoyer la socket à leur bibliothèque SSL ou de la traiter comme une violation du protocole s'ils voient qu'ils ont déjà des octets supplémentaires.

Un message initial SSLRequest peut aussi être utilisé dans une connexion en cours d'ouverture pour envoyer un message CancelRequest.

Alors que le protocole lui-même ne fournit pas de moyens pour que le serveur force un chiffrement SSL, l'administrateur peut configurer le serveur de telle façon qu'il rejette les sessions non chiffrées. Cela se fait pendant la vérification de l'authentification.

54.2.10. Chiffrement GSSAPI des sessions

Si PostgreSQL a été compilé avec le support de GSSAPI, les communications client/backend peuvent être chiffrées en utilisant GSSAPI. Ceci fournit une sécurité de la communication dans les environnements où les attaquants pourraient être en mesure de capturer le trafic de la session. Pour plus d'informations sur le chiffrement des sessions PostgreSQL avec GSSAPI, voir Section 19.10.

Pour initier une connexion chiffrée avec GSSAPI, le client doit au départ envoyer un message GSSENCRequest plutôt qu'un message StartupMessage. Le serveur répond avec un seul octet contenant G ou N, indiquant s'il est, respectivement, disposé ou non pour réaliser un chiffrement GSSAPI. Le client pourrait fermer la connexion à ce stage s'il n'est pas satisfait par la réponse. Pour continuer après un G, en utilisant les fonctions C de GSSAPI comme indiqué dans la RFC 2744 ou un équivalent, le client réalise une initialisation de GSSAPI en appelant gss_init_sec_context() dans une boucle et en envoyant le résultat au serveur, en commençant avec une entrée vide, et en continuant avec chaque résultat du serveur jusqu'à la fin. Lors de l'envoi de résultats de gss_init_sec_context() au serveur, il ajoute la longueur du message sous la forme d'un entier sous quatre octets dans l'ordre des octets du réseau. Pour continuer après un N, il envoie le message habituel StartupMessage puis continue sans chiffrement. (Une autre solution est de lancer un message SSLRequest après une réponse N pour tenter l'utilisation d'un chiffrement SSL à la place du chiffrement GSSAPI.)

Le client doit aussi être préparé à gérer une réponse ErrorMessage pour GSSENCRequest provenant du serveur. Ceci n'arrivera que si le serveur est antérieur à l'ajout du support du chiffrement GSSAPI dans PostgreSQL. Dans ce cas, la connexion doit être termée, mais le client peut choisir d'ouvrir une nouvelle connexion et de continuer sans demander de chiffrement GSSAPI.

Quand le chiffrement GSSAPI peut être réalisé, le serveur est supposé envoyer l'octet G, puis attendre que le client initie une poignée de main GSSAPI. Si des octets supplémentaires sont disponibles en lecture, cela signifie probablement qu'une attaque de type man-in-the-middle est en cours et tente de réaliser la technique buffer-stuffing (CVE-2021-23222). Les clients doivent être codés soit pour lire exactement un octet de la socket avant de renvoyer la socket à leur bibliothèque GSSAPI ou de la traiter comme une violation du protocole s'ils voient qu'ils ont déjà des octets supplémentaires à lire.

Un message initial GSSENCRequest peut aussi être utilisé dans une connexion en cours d'ouverture pour envoyer un message CancelRequest.

Une fois que le chiffrement GSSAPI a été établi avec succès, le client utilise la fonction gss_wrap() pour chiffrer le message StartupMessage habituel et toutes les données qui suivent, ajoutant la longueur du résultat de gss_wrap() sous la forme d'un entier sous quatre octets dans l'ordre d'octets du réseau à la charge chiffrée réelle. Notez que le serveur acceptera seulement des paquets chiffrés provenant du client qui font moins de 16 Ko ; gss_wrap_size_limit() devrait être utilisé par le client pour déterminer la taille du message non chiffré qui sera contenu dans cette limite, et les messages plus larges devront être divisés en plusieurs appels à gss_wrap(). Les segments typiques sont 8 Ko de données non chiffrées, résultant en des paquets chiffrés légèrement supérieurs à 8 Ko mais bien contenu dans les 16 Ko maximum. Il est attendu que le serveur n'envoie pas de paquets de plus de 16 ;Ko au client.

Alors que le protocole lui-même ne fournit pas de moyens pour que le serveur force un chiffrement GSSAPI, l'administrateur peut configurer le serveur de telle façon qu'il rejette les sessions non chiffrées. Cela se fait pendant la vérification de l'authentification.