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 53.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.
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 :
La demande de connexion a été rejetée. Le serveur ferme immédiatement la connexion.
L'échange d'authentification s'est terminé avec succès.
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é.
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.
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.)
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.
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.
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.
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 53.3 pour les détails.
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.
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.
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 :
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.
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 53.2.7 pour plus de détails. Le client ne doit pas répondre à ce message, mais doit continuer à attendre un message ReadyForQuery.
Le démarrage est terminé. Le client peut maintenant exécuter des commandes.
Le démarrage a échoué. La connexion est fermée après l'envoi de ce message.
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.
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 :
Une commande SQL terminée normalement.
Le backend est prêt à copier des données du client vers une table ; voir Section 53.2.6.
Le backend est prêt à copier les données d'une table vers le client ; voir Section 53.2.6.
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.
Un des ensembles de lignes renvoyés par une requête
SELECT
, FETCH
, etc.
Une chaîne a été reconnue à la place d'une requête.
Une erreur est survenue.
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.
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 53.2.6.
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 53.2.7 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.
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.
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.
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
.
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.
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.)
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.
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.
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.
L'utilisation du protocole de requête étendue autorise les pipelines, autrement dit l'envoi d'une série de requêtes sans attendre que les premières se terminent. Ceci réduit le nombre d'aller/retour réseau nécessaire pour terminer une série d'opérations. Néanmoins, l'utilisateur doit faire attention au comportement souhaité si une des étapes échoue car les requêtes suivantes seront déjà envoyées au serveur.
Une façon de gérer cela est de transformer la série complète de requête en
une seule transaction, donc de l'entourer des commandes
BEGIN
... COMMIT
. Cela n'aide
cependant pas les personnes qui souhaiteraient que certaines commandes
soient validées indépendamment des autres.
Le protocole de requête étendue fournit un autre moyen pour gérer cette
problématique. Il s'agit d'oublier d'envoyer les messages Sync entre les
étapes qui sont dépendantes. Comme, après une erreur, le moteur ignorera les
messages des commandes jusqu'à ce qu'il trouve un message Sync, cela
autorise les commandes ultérieures d'un pipeline d'être automatiquement
ignorées si une commande précédente échoue, sans que le client ait à gérer
cela explicitement avec des commandes BEGIN
et
COMMIT
. Les segments à valider indépendamment dans le
pipeline peuvent être séparées par des messages Sync.
Si le client n'a pas exécuté un BEGIN
explicite, alors
chaque Sync implique un COMMIT
implicite si les étapes
prédédentes ont réussi ou un ROLLBACK
implicite si elles
ont échoué. Néanmoins, il existe quelques commandes DDL (comme
CREATE DATABASE
) qui ne peuvent pas être exécutées dans
un bloc de transaction. Si une de ces commandes est exécutée dans un
pipeline, cela échouera sauf s'il s'agit de la première commande du pipeline.
De plus, en cas de succès, cela forcera une validation immédiate pour
préserver la cohérence de la base. De ce fait, un Sync suivant immédiatement une des ces
commandes n'aura pas d'effet autre que de répondre avec ReadyForQuery.
Lors de l'utilisation de cette méthode, la fin du pipelin doit être déterminée en comptant les messages ReadyForQuery et en attendant que cela atteigne le nombre de Sync envoyés. Compter les réponses de fin de commande n'est pas fiable car certaines commandes pourraient être ignorées et donc ne pas produire de message de fin.
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.
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 :
Une erreur est survenue
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.)
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.
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.
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 53.2.7). 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 53.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.)
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 :
application_name | is_superuser |
client_encoding | scram_iterations |
DateStyle | server_encoding |
default_transaction_read_only | server_version |
in_hot_standby | session_authorization |
integer_datetimes | standard_conforming_strings |
IntervalStyle | TimeZone |
(default_transaction_read_only
et
in_hot_standby
ne sont pas rapportés avant la version
14 ; scram_iterations
n'est par rapporté avant la
version 16.)
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.
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.
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.
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.
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 18.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. Le client ne doit pas afficher ce message d'erreur à l'utilisateur/application car le serveur n'a pas été authentifié (CVE-2024-10977). 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.
De la même façon, le serveur s'attend à ce que le client ne commence pas la négociation SSL tant qu'il n'a pas reçu la réponse en un octet du serveur à la demande de SSL. Si le client commence la négociation SSL immédiatement, sans attendre la réception de la réponse du serveur, cela peut réduire la latence de connexion d'un aller/retour. Néanmoins, cela a un coût : ne pas être capable de gérer le cas où le serveur envoie une réponse négative à la demande de SSL. Dans ce cas, au lieu de continuer avec une connexion GSSAPI ou non chiffrée ou une erreur de procotole, le serveur se déconnectera tout simplement.
Un message initial SSLRequest peut aussi être utilisé dans une connexion en cours d'ouverture pour envoyer un message CancelRequest.
Une autre façon d'initier le chiffrement SSL est disponible. Le serveur reconnaitra les connexions qui commencent immédiatement une négociation SSL sans paquet SSLRequest. Une fois la connexion SSL établie, le serveur attendra un paquet standard startup-request et continuera la négociation sur un canal chiffré. Dans ce cas, toute autre requête de chiffrement sera refusée. Cette méthode n'est pas préférée pour les outils généralistes car elle ne peut pas négotier le meilleur chiffrement de la connexion et ne peut pas gérer les connexions non chiffrées. Cependant, c'est utile pour des environnements où le serveur et le client sont contrôlés ensemble. Dans ce cas, cela évite une latence d'un aller/retour, et permet l'utilisation d'outils réseaux dépendants de connexions standards SSL. Lors de l'utilisation de connexions SSL de ce style, le client doit utiliser l'extension ALPN définie par la RFC 7301 pour se protéger contre les attaques de confusion de protocole. Le protocole PostgreSQL est enregistré sous le nom "postgresql" sur le resgistre IANA TLS ALPN Protocol IDs.
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.
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 18.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. Le client ne doit pas afficher ce message d'erreur à l'utilisation/application carle serveur n'a pas été authentifié (CVE-2024-10977). 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.