Cette section décrit le flux des messages et la sémantique de chaque type de message (les détails concernant la représentation exacte de chaque message apparaissent dans Section 52.7). Il existe différents sous-protocoles en fonction de l'état de la connexion : lancement, requête, appel de fonction, COPY et clôture. Il existe aussi des provisions spéciales pour les opérations asynchrones (incluant les réponses aux notifications et les annulations de commande), qui peuvent arriver à tout moment après la phase de lancement.
Pour débuter 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 à laquelle le client souhaite se connecter ; il
identifie aussi la version particulière du protocole à utiliser
(optionnellement, le message de démarrage peut inclure des précisions
supplémentaires pour les paramètres d'exécution). Le serveur utilise
ces informations et le contenu des fichiers de configuration
(tels que pg_hba.conf
) pour déterminer si la connexion
est acceptable et quelle éventuelle authentification supplémentaire est requise.
Le serveur envoie ensuite le message de demande d'authentification approprié, auquel le client doit répondre avec le message de réponse d'authentification adapté (tel un mot de passe). Pour toutes les méthodes d'authentification, sauf GSSAPI, SSPI et SASL, il y a au maximum une requête et une réponse. Avec certaines méthodes, aucune réponse du client n'est nécessaire, et aucune demande d'authentification n'est alors effectuée. Pour GSSAPI, SSPI et SASL, plusieurs échanges de paquets peuvent être nécessaires pour terminer l'authentification.
Le cycle d'authentification se termine lorsque le serveur rejette la tentative de connexion (ErrorResponse) ou l'accepte (AuthenticationOk).
Les messages possibles du serveur dans cette phase sont :
La tentative 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 alors prendre part à un dialogue d'authentification Kerberos V5 (spécification Kerberos, non décrite ici) avec le serveur. En cas de succès, le serveur répond AuthenticationOk, ErrorResponse sinon. Ce n'est plus supporté.
Le client doit alors envoyer un PasswordMessage contenant le mot de passe en clair. Si le mot de passe est correct, le serveur répond AuthenticationOk, ErrorResponse sinon.
Le client doit envoyer un PasswordMessage contenant le mot de passe
(avec le nom de l'utilisateur) chiffré en MD5, puis chiffré de
nouveau avec un salt aléatoire sur 4 octets indiqué dans le message
AuthenticationMD5Password. S'il s'agit du bon mot de passe, le
serveur répond avec un AuthenticationOk, sinon il répond avec un
ErrorResponse. Le PasswordMessage réel peut être calculé en SQL
avec concat('md5',
md5(concat(md5(concat(password, username)), random-salt)))
.
(Gardez en tête que la fonction md5()
renvoie
son résultat sous la forme d'une chaîne hexadécimale.)
Cette réponse est possible uniquement pour les connexions locales de domaine Unix sur les plateformes qui supportent les messages de légitimation SCM. Le client doit fournir un message de légitimation SCM, puis envoyer une donnée d'un octet. Le contenu de cet octet importe peu ; il n'est utilisé que pour s'assurer que le serveur attend assez longtemps pour recevoir le message de légitimation. Si la légitimation est acceptable, le serveur répond AuthenticationOk, ErrorResponse sinon. (Ce type de message n'est envoyé que par des serveurs dont la version est antérieure à la 9.1. Il pourrait être supprimé de la spécification du protocole.)
L'interface doit maintenant initier une négociation GSSAPI. L'interface doit envoyer un GSSResponse avec la première partie du flux de données GSSAPI en réponse à ceci. Si plus de messages sont nécessaires, le serveur répondra avec AuthenticationGSSContinue.
L'interface doit maintenant initier une négociation SSPI. L'interface doit envoyer un GSSResponse avec la première partie du flux de données SSPI en réponse à ceci. Si plus de messages sont nécessaires, le serveur répondra avec AuthenticationGSSContinue.
Ce message contient les données de la réponse de l'étape précédente pour la négociation GSSAPI ou SSPI (AuthenticationGSS ou un précédent AuthenticationGSSContinue). Si les données GSSAPI dans ce message indique que plus de données sont nécessaire pour terminer l'authentification, l'interface doit envoyer cette donnée dans un autre GSSResponse. Si l'authentification GSSAPI ou SSPI est terminée par ce message, le serveur enverra ensuite AuthenticationOk pour indiquer une authentification réussie ou ErrorResponse pour indiquer l'échec.
L'interface doit maintenant initier une négociation SASL en utilisant un des mécanismes SASL listés dans le message. L'interface enverra un 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 AuthenticationSASLContinue. Voir Section 52.3 pour les détails.
Ce message contient les données de challenge provenant de l'étape précédente de la négociation SASL (AuthenticationSASL ou un précédent AuthenticationSASLContinue). L'interface doit répondre avec un message SASLResponse.
L'authentification SASL s'est terminée avec les données supplémentaires du mécanisme sélectionné pour le client. Le serveur enverra ensuite AuthenticationOk pour indiquer le succès de l'authentification ou un ErrorResponse pour indiquer l'échec. Ce message est seulement envoyé si le mécanisme SASL indique l'envoi de données supplémentaires du serveur au client à la fin.
Le serveur ne supporte par la version mineure du protocole réclamée
par le client mais supporte une version précédente du protocole. Ce
message indique la plus haute version mineure supportée. Ce message
sera aussi envoyé si le client demande des options de protocole non
supportées (commençant par _pq_.
) dans le paquet de
démarrage. Ce message sera suivi par un ErrorResponse ou un message
indiquant le succès ou l'échec de l'authentification.
Si le client ne supporte pas la méthode d'authentification demandée par le serveur, il doit immédiatement fermer la connexion.
Après la réception du message AuthenticationOk, le client attend d'autres messages du serveur. Au cours de cette phase, un processus serveur est lancé et le client est simplement en attente. Il est encore possible que la tentative de lancement échoue (ErrorResponse) ou que le serveur décline le support de la version mineure du protocole demandée (NegotiateProtocolVersion) mais, dans la plupart des cas, le serveur enverra les messages ParameterStatus, BackendKeyData et enfin ReadyForQuery.
Durant cette phase, le serveur tentera d'appliquer tous les paramètres d'exécution supplémentaires qui ont été fournis par le message de lancement. En cas de succès, ces valeurs deviennent les valeurs par défaut de la session. Une erreur engendre ErrorResponse et déclenche la sortie.
Les messages possibles du serveur dans cette phase sont :
Ce message fournit une clé secrète que le client doit conserver s'il souhaite envoyer des annulations de requêtes par la suite. Le client ne devrait pas répondre à ce message, mais continuer à attendre un message ReadyForQuery.
Ce message informe le client de la configuration actuelle (initiale)
des paramètres du serveur, tels client_encoding
ou
datestyle
. Le client peut ignorer ce message ou enregistrer
la configuration pour ses besoins futurs ; voir
Section 52.2.7 pour plus de détails. Le client ne
devrait pas répondre à ce message mais continuer à attendre un message
ReadyForQuery.
Le lancement est terminé. Le client peut dès lors envoyer des commandes.
Le lancement a échoué. La connexion est fermée après l'envoi de ce message.
Un message d'avertissement a été envoyé. Le client devrait afficher ce message mais continuer à attendre un ReadyForQuery ou un ErrorResponse.
Le même message ReadyForQuery est envoyé à chaque cycle de commande. En fonction des besoins de codage du client, il est possible de considérer ReadyForQuery comme le début d'un cycle de commande, ou de le considérer comme terminant la phase de lancement et chaque cycle de commande.
En protocole Simple Query, un cycle est initié par le client qui envoie un message Query au serveur. Le message inclut une commande SQL (ou plusieurs) exprimée comme une chaîne texte. Le serveur envoie, alors, un ou plusieurs messages de réponse dépendant du contenu de la chaîne représentant la requête et enfin un message ReadyForQuery. ReadyForQuery informe le client qu'il peut envoyer une nouvelle commande. Il n'est pas nécessaire que le client attende ReadyForQuery avant de lancer une autre commande mais le client prend alors la responsabilité de ce qui arrive si la commande précédente échoue et que les commandes suivantes, déjà lancées, réussissent.
Les messages de réponse du serveur sont :
Commande SQL terminée normalement.
Le serveur est prêt à copier des données du client vers une table ; voir Section 52.2.6.
Le serveur est prêt à copier des données d'une table vers le client ; voir Section 52.2.6.
Indique que des lignes vont être envoyées en réponse à une
requête select
, fetch
...
Le contenu de ce message décrit le placement des colonnes dans les
lignes. Le contenu est suivi d'un message DataRow pour chaque ligne envoyée
au client.
Un des ensembles de lignes retournés par une requête
select
, fetch
...
Une chaîne de requête vide a été reconnue.
Une erreur est survenue.
Le traitement d'une requête est terminé. Un message séparé est envoyé pour l'indiquer parce qu'il se peut que la chaîne de la requête contienne plusieurs commandes SQL. CommandComplete marque la fin du traitement d'une commande SQL, pas de la chaîne complète. ReadyForQuery sera toujours envoyé que le traitement se termine avec succès ou non.
Un message d'avertissement concernant la requête a été envoyé. Les avertissements sont complémentaires des autres réponses, le serveur continuera à traiter la commande.
La réponse à une requête select
(ou à d'autres requêtes, telles
explain
ou show
, qui retournent des ensembles de
données) consiste normalement en un RowDescription, plusieurs messages
DataRow (ou aucun) et pour finir un CommandComplete. copy
depuis
ou vers le client utilise un protocole spécial décrit dans
Section 52.2.6. Tous les autres types de requêtes produisent
uniquement un message CommandComplete.
Puisqu'une chaîne de caractères peut contenir plusieurs requêtes (séparées par des points virgules), il peut y avoir plusieurs séquences de réponses avant que le serveur ne finisse de traiter la chaîne. ReadyForQuery est envoyé lorsque la chaîne complète a été traitée et que le serveur est prêt à accepter une nouvelle chaîne de requêtes.
Si une chaîne de requêtes complètement vide est reçue (aucun contenu autre que des espaces fines), la réponse sera EmptyQueryResponse suivie de ReadyForQuery.
En cas d'erreur, ErrorResponse est envoyé suivi de ReadyForQuery. Tous les traitements suivants de la chaîne sont annulés par ErrorResponse (quelque soit le nombre de requêtes restant à traiter). Ceci peut survenir au milieu de la séquence de messages engendrés par une requête individuelle.
En mode Simple Query, les valeurs récupérées sont toujours au format
texte, sauf si la commande est un fetch
sur un curseur déclaré
avec l'option binary
. Dans ce cas, les valeurs récupérées sont
au format binaire. Les codes de format donnés dans le message RowDescription
indiquent le format utilisé.
Un client doit être préparé à accepter des messages ErrorResponse et NoticeResponse quand bien même il s'attendrait à un autre type de message. Voir aussi Section 52.2.7 concernant les messages que le client pourrait engendrer du fait d'événements extérieurs.
La bonne pratique consiste à coder les clients dans un style machine-état qui acceptera tout type de message à tout moment plutôt que de parier sur la séquence exacte des messages.
Lorsqu'un message Simple Query contient plus d'une instruction SQL (séparées par des points-virgules), ces instructions sont exécutées comme une seule transaction à moins que des commandes explicites de contrôle des transactions ne soient incluses 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);
l'échec de la division par zéro dans le SELECT
forcera
le retour en arrière du premier INSERT
. De plus, comme
l'exécution du message est abandonnée à la première erreur, le second
INSERT
n'est jamais executé.
Si au lieu de cela, le message contient :
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 second INSERT
et le SELECT
sont toujours traités comme une seule
transaction, de sorte que l'échec de la division par zéro fera annuler
(rollback
) le second INSERT
, mais
pas le premier.
Ce comportement est implémenté en exécutant les instructions dans un message
de requête multi-instructions dans un
bloc de transaction implicite, à moins qu'il n'y ait
un bloc de transaction explicite dans lequel elles puissent être exécutées.
La principale différence entre un bloc de transaction implicite et un bloc
normal est qu'un bloc implicite est fermé automatiquement à la fin du
message de requête, soit par un COMMIT
implicite s'il
n'y a pas d'erreur, soit par un ROLLBACK
implicite s'il
y a une erreur. Ceci est similaire au COMMIT
ou
ROLLBACK
implicite qui se produit pour une instruction
exécutée par elle-même (lorsqu'elle n'est pas dans un bloc de transaction).
Si la session se trouve déjà dans un bloc de transaction à la suite d'un
BEGIN
dans un message précédent, le message de requête
poursuit simplement ce bloc de transaction, que le message contienne une
ou plusieurs instructions. Toutefois, si le message de requête contient un
COMMIT
ou un ROLLBACK
fermant le bloc
de transaction existant, toutes les instructions suivantes sont exécutées
dans un bloc de transaction implicite. À l'inverse, si un BEGIN
apparaît dans un message de requête multi-instructions, il lance un bloc
de transaction normal qui ne sera terminé que par un COMMIT
ou un ROLLBACK
explicite ; que celui-ci apparaisse dans
ce message de requête ou dans un autre. Si le BEGIN
suit certaines instructions qui ont été exécutées comme un bloc de transaction
implicite, ces instructions ne sont pas immédiatement validées ; en fait,
ils sont inclus rétroactivement dans le nouveau bloc de transaction normal.
Un COMMIT
ou ROLLBACK
apparaissant dans
un bloc de transaction implicite est exécuté normalement, fermant ainsi le bloc
implicite ; cependant, un avertissement sera levé puisqu'un
COMMIT
ou ROLLBACK
qui est utilisé sans
un BEGIN
le précédent peut être une erreur. Si d'autres
instructions suivent, un nouveau bloc de transaction implicite sera lancé
pour elles.
Les points de sauvegarde (SAVEPOINT
) ne sont pas autorisés
dans un bloc de transaction implicite, car ils seraient en conflit avec le
comportement de fermeture automatique du bloc en cas d'erreur.
N'oubliez pas que, quelle que soit la commande de contrôle de transaction présente, l'exécution du message de requête s'arrête dès la première erreur. Par exemple, si pour un message Simple Query l'on donne :
BEGIN; SELECT 1/0; ROLLBACK;
la session sera laissée à l'intérieur d'un « bloc de transaction
normal » échoué, puisque le ROLLBACK
n'est pas
atteint après l'erreur de la division par zéro.
Un autre ROLLBACK
sera donc nécessaire pour restaurer
la session à un état utilisable.
Il y a un autre comportement qu'il faut souligner, l'analyse lexicale et syntaxique initiale est effectuée sur toute la chaîne de la requête avant qu'elle ne soit exécutée. Ainsi, de simples erreurs dans une instruction (comme un mot-clé mal orthographié) empêchent l'exécution de toutes les instructions. Ceci est transparent pour les utilisateurs puisque les instructions seraient annulées de toute façon lorsqu'elles sont exécutées dans un bloc de transaction implicite. Toutefois, elle peut être visible lorsque vous tentez d'effectuer plusieurs transactions dans une requête multi-instructions. Par exemple, si une faute de frappe a transformé notre exemple précédent en :
BEGIN; INSERT INTO mytable VALUES(1); COMMIT; INSERT INTO mytable VALUES(2); SELCT 1/0;
alors aucune des instructions ne sera exécutée, la différence visible est
que le premier INSERT
n'est pas validé (COMMIT
).
Les erreurs détectées lors de l'analyse sémantique ou plus tard, telles
qu'un nom de table ou de colonne mal orthographié, n'ont pas cet effet.
Le protocole Extended Query divise le protocole Simple Query décrit ci-dessus en plusieurs étapes. Les résultats des étapes de préparation peuvent être réutilisés plusieurs fois pour plus d'efficacité. De plus, des fonctionnalités supplémentaires sont disponibles, telles que la possibilité de fournir les valeurs des données comme des paramètres séparés au lieu d'avoir à les insérer directement dans une chaîne de requêtes.
Dans le protocole étendu, le client envoie tout d'abord un message Parse qui contient une chaîne de requête, optionnellement quelques informations sur les types de données aux emplacements des paramètres, et le nom de l'objet de destination d'une instruction préparée (une chaîne vide sélectionne l'instruction préparée sans nom). La réponse est soit ParseComplete soit ErrorResponse. Les types de données des paramètres peuvent être spécifiés par l'OID ; dans le cas contraire, l'analyseur tente d'inférer les types de données de la même façon qu'il le ferait pour les constantes chaînes littérales non typées.
Un type de paramètre peut être laissé non spécifié en le positionnant
à O, ou en créant un tableau d'OID de type plus court que le nombre de
paramètres ($
n
) utilisés
dans la chaîne de requête.
Un autre cas particulier est d'utiliser void
comme type de
paramètre (c'est à dire l'OID du pseudo-type void
). Cela
permet d'utiliser des paramètres dans des fonctions en tant qu'argument
OUT.
Généralement, il n'y a pas de contexte dans lequel void
peut être utilisé, mais si un tel paramètre apparaît dans les arguments
d'une fonction, il sera simplement ignoré.
Par exemple, un appel de fonction comme
foo($1,$2,$3,$4)
peut correspondre à une fonction avec
2 arguments IN et 2 autres OUT si $3
et
$4
sont spécifiés avec le type void
.
La chaîne contenue dans un message Parse ne peut pas inclure plus d'une instruction SQL, sinon une erreur de syntaxe est rapportée. Cette restriction n'existe pas dans le protocole Simple Query, mais est présente dans le protocole étendu. En effet, permettre aux instructions préparées ou aux portails de contenir de multiples commandes compliquerait inutilement le protocole.
En cas de succès de sa création, une instruction préparée nommée dure
jusqu'à la fin de la session courante, sauf si elle est détruite
explicitement. Une instruction préparée non nommée ne dure que jusqu'à
la prochaine instruction Parse spécifiant l'instruction non nommée comme
destination. Un message Simple Query détruit également l'instruction non
nommée. Les instructions préparées nommées doivent être explicitement
closes avant de pouvoir être redéfinies par un autre message Parse. Ce n'est
pas obligatoire pour une instruction non nommée. Il est également possible
de créer des instructions préparées nommées, et d'y accéder, en ligne de
commandes SQL à l'aide des instructions prepare
et
execute
.
Dès lors qu'une instruction préparée existe, elle est déclarée exécutable
par un message Bind. Le message Bind donne le nom de l'instruction préparée
source (une chaîne vide désigne l'instruction préparée non nommée), le nom
du portail destination (une chaîne vide désigne le portail non nommé) et les
valeurs à utiliser pour tout emplacement de paramètres présent dans
l'instruction préparée. L'ensemble des paramètres fournis doit
correspondre à ceux nécessaires à l'instruction préparée. (Si des
paramètres sont déclarés à void
dans le message Parse,
il faut passer NULL comme valeur associée dans le message Bind.)
Bind spécifie aussi le format à utiliser pour toutes les données
renvoyées par la requête ; le format peut être spécifié globalement
ou par colonne. La réponse est soit BindComplete, soit ErrorResponse.
Le choix entre sortie texte et binaire est déterminé par les codes
de format donnés dans Bind, quelle que soit la commande SQL impliquée.
L'attribut BINARY
dans les déclarations du curseur n'est pas
pertinent lors de l'utilisation du protocole Extended Query.
La planification de la requête survient généralement quand le message Bind est traité. Si la requête préparée n'a pas de paramètre ou si elle est exécutée de façon répétée, le serveur peut sauvegarder le plan créé et le ré-utiliser lors des appels suivants à Bind pour la même requête préparée. Néanmoins, il ne le fera que s'il estime qu'un plan générique peut être créé en étant pratiquement aussi efficace qu'un plan dépendant des valeurs des paramètres. Cela arrive de façon transparente en ce qui concerne le protocole.
En cas de succès de sa création, un objet portail nommé dure jusqu'à la fin
de la transaction courante sauf s'il est explicitement détruit. Un
portail non nommé est détruit à la fin de la transaction ou dès la prochaine
instruction Bind spécifiant le portail non nommé comme destination.
(À noter qu'un message Simple Query détruit également le portail non nommé.) Les
portails nommés doivent être explicitement fermés avant de pouvoir être
redéfinis par un autre message Bind. Cela n'est pas obligatoire pour le portail non
nommé. Il est également possible de créer des portails nommés, et d'y
accéder, en ligne de commandes SQL à l'aide des instructions
declare cursor
et fetch
.
Dès lors qu'un portail existe, il peut être exécuté à l'aide d'un message Execute. Ce message spécifie le nom du portail (une chaîne vide désigne le portail non nommé) et un nombre maximum de lignes de résultat (zéro signifiant la « récupération de toutes les lignes »). Le nombre de lignes de résultat a seulement un sens pour les portails contenant des commandes qui renvoient des ensembles de lignes ; dans les autres cas, la commande est toujours exécutée jusqu'à la fin et le nombre de lignes est ignoré. Les réponses possibles d'Execute sont les mêmes que celles décrites ci-dessus pour les requêtes lancées via le protocole Simple Query, si ce n'est qu'Execute ne cause pas l'envoi de ReadyForQuery ou de RowDescription.
Si Execute se termine avant la fin de l'exécution d'un portail (du fait d'un nombre de lignes de résultats différent de zéro), il enverra un message PortalSuspended ; la survenue de ce message indique au client qu'un autre Execute devrait être lancé sur le même portail pour terminer l'opération. Le message CommandComplete indiquant la fin de la commande SQL n'est pas envoyé avant l'exécution complète du portail. Une phase Execute est toujours terminée par la survenue d'un seul de ces messages : CommandComplete, EmptyQueryResponse (si le portail a été créé à partir d'une chaîne de requête vide), ErrorResponse ou PortalSuspended.
À la réalisation complète de chaque série de messages Extended Query,
le client doit lancer un message Sync. Ce message sans paramètre oblige
le serveur à fermer la transaction courante si elle n'est pas à l'intérieur
d'un bloc de transaction begin
/commit
(« fermer » signifiant valider en l'absence d'erreur ou annuler sinon).
Une réponse ReadyForQuery est alors envoyée. Le but de Sync est de fournir
un point de resynchronisation pour les récupérations d'erreurs. Quand une
erreur est détectée lors du traitement d'un message Extended Query, le
serveur lance ErrorResponse, puis lit et annule les messages jusqu'à ce
qu'un Sync soit atteint. Il envoie ensuite ReadyForQuery et retourne au
traitement normal des messages. Aucun échappement n'est réalisé si une erreur
est détectée lors du traitement de Sync -- l'unicité du
ReadyForQuery envoyé pour chaque Sync est ainsi assurée.
Sync n'impose pas la fermeture d'un bloc de transactions ouvert avec
begin
. Cette situation est détectable car le message
ReadyForQuery inclut le statut de la transaction.
En plus de ces opérations fondamentales, requises, il y a plusieurs opérations optionnelles qui peuvent être utilisées avec le protocole Extended Query.
Le message Describe (variante de portail) spécifie 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 renvoyées par l'exécution du portail ; ou un message NoData si le portail ne contient pas de requête renvoyant des lignes ; ou ErrorResponse si le portail n'existe pas.
Le message Describe (variante d'instruction) spécifie le nom d'une instruction préparée existante (ou une chaîne vide pour l'instruction préparée non nommée). La réponse est un message ParameterDescription décrivant les paramètres nécessaires à l'instruction, suivi d'un message RowDescription décrivant les lignes qui seront renvoyées lors de l'éventuelle exécution de l'instruction (ou un message NoData si l'instruction ne renvoie pas de lignes). ErrorResponse est retourné si l'instruction préparée n'existe pas. Comme Bind n'a pas encore été exécuté, les formats à utiliser pour les lignes retournées ne sont pas encore connues du serveur ; dans ce cas, les champs du code de format dans le message RowDescription seront composés de zéros.
Dans la plupart des scénarios, le client devra exécuter une des variantes de Describe avant de lancer Execute pour s'assurer qu'il sait interpréter les résultats reçus.
Le message Close ferme une instruction préparée ou un portail et libère les ressources. L'exécution de Close sur une instruction ou un portail inexistant ne constitue pas une erreur. La réponse est en général CloseComplete mais peut être ErrorResponse si une difficulté quelconque est rencontrée lors de la libération des ressources. Clore une instruction préparée ferme implicitement tout autre portail ouvert construit à partir de cette instruction.
Le message Flush n'engendre pas de sortie spécifique, mais force le serveur à délivrer toute donnée restante dans les tampons de sortie. Un Flush doit être envoyé après toute commande Extended Query, à l'exception de Sync, si le client souhaite examiner le résultat de cette commande avant de lancer d'autres commandes. Sans Flush, les messages retournés par le serveur seront combinés en un nombre minimum de paquets pour minimiser la charge réseau.
Le message Simple Query est approximativement équivalent à la séquence Parse, Bind, Describe sur un portail, Execute, Close, Sync utilisant les objets de l'instruction préparée ou du portail, non nommés et sans paramètres. Une différence est l'acceptation de plusieurs instructions SQL dans la chaîne de requêtes, la séquence bind/describe/execute étant automatiquement réalisée pour chacune, successivement. Il en diffère également en ne retournant pas les messages ParseComplete, BindComplete, CloseComplete ou 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 (NDT : Function Call dans la
version originale) permet au client d'effectuer un appel direct à toute
fonction du catalogue système pg_proc
de la base de
données. Le client doit avoir le droit d'exécution de la fonction.
Le sous-protocole d'appel de fonction est une fonctionnalité qu'il vaudrait
probablement mieux éviter dans tout nouveau code. Des résultats similaires
peuvent être obtenus en initialisant une instruction préparée qui lance
select function($1, ...)
. Le cycle de l'appel de fonction peut
alors être remplacé par Bind/Execute.
Un cycle d'appel de fonction est initié par le client envoyant un message FunctionCall au serveur. Le serveur envoie alors un ou plusieurs messages de réponse en fonction des résultats de l'appel de la fonction et finalement un message de réponse ReadyForQuery. ReadyForQuery informe le client qu'il peut envoyer en toute sécurité une nouvelle requête ou un nouvel appel de fonction.
Les messages de réponse possibles du serveur sont :
Une erreur est survenue.
L'appel de la fonction est terminé et a retourné le résultat donné dans le message. Le protocole d'appel de fonction ne peut gérer qu'un résultat scalaire simple, pas un type ligne ou un ensemble de résultats.
Le traitement de l'appel de fonction est terminé. ReadyForQuery sera toujours envoyé, que le traitement se termine avec succès ou avec une erreur.
Un message d'avertissement relatif à l'appel de fonction a été retourné. Les avertissements sont complémentaires des autres réponses, c'est-à-dire que le serveur continuera à traiter la commande.
La commande copy
permet des transferts rapides de données en lot
vers ou à partir du serveur. Les opérations Copy-in et Copy-out basculent
chacune la connexion dans un sous-protocole distinct qui existe jusqu'à la
fin de l'opération.
Le mode Copy-in (transfert de données vers le serveur) est initié quand le
serveur exécute une instruction SQL copy from stdin
. Le serveur
envoie une message CopyInResponse au client. Le client peut alors envoyer
zéro (ou plusieurs) message(s) CopyData, formant un flux de données en
entrée (il n'est pas nécessaire que les limites du message aient un
rapport avec les limites de la ligne, mais cela est souvent un choix
raisonnable). Le client peut terminer le mode Copy-in en envoyant un
message CopyDone (permettant une fin avec succès) ou un message CopyFail
(qui causera l'échec de l'instruction SQL copy
avec une erreur).
Le serveur retourne alors au mode de traitement de la commande précédant le
début de copy
, soit les protocoles Simple Query ou Extended Query. Il
enverra enfin CommandComplete (en cas de succès) ou ErrorResponse (sinon).
Si le serveur détecte une erreur en mode copy-in (ce qui inclut la
réception d'un message CopyFail), il enverra un message ErrorResponse.
Si la commande copy
a été lancée à l'aide d'un message
Extended Query, le serveur annulera les messages du client jusqu'à
ce qu'un message Sync soit reçu. Il enverra alors un message ReadyForQuery
et retournera dans le mode de fonctionnement normal. Si la commande
copy
a été lancée dans un message
Simple Query, le reste de ce message est annulé et ReadyForQuery est envoyé.
Dans tous les cas, les messages CopyData, CopyDone ou CopyFail suivants
envoyés par l'interface seront simplement annulés.
Le serveur ignorera les messages Flush et Sync reçus en mode copy-in. La
réception de tout autre type de messages hors-copie constitue une
erreur qui annulera l'état Copy-in, comme cela est décrit plus haut.
L'exception pour Flush et Sync est faite pour les bibliothèques clientes
qui envoient systématiquement Flush ou Sync après un message Execute sans
vérifier si la commande à exécuter est copy from stdin
.
Le mode Copy-out (transfert de données à partir du serveur) est initié
lorsque le serveur exécute une instruction SQL copy to stdout
.
Le moteur envoie un message CopyOutResponse au client suivi de zéro (ou
plusieurs) message(s) CopyData (un par ligne), suivi de CopyDone.
Le serveur retourne ensuite au mode de traitement de commande dans lequel il
se trouvait avant le lancement de copy
et envoie CommandComplete.
Le client ne peut pas annuler le transfert (sauf en fermant la connexion ou en
lançant une requête d'annulation, Cancel), mais il peut ignorer les messages
CopyData et CopyDone non souhaités.
Si le serveur détecte une erreur en mode Copy-out, il enverra un message ErrorResponse et retournera dans le mode de traitement normal. Le client devrait traiter la réception d'un message ErrorResponse comme terminant le mode « copy-out ».
Il est possible que les messages NoticeResponse et ParameterStatus soient entremêlés avec des messages CopyData ; les interfaces doivent gérer ce cas, et devraient être aussi préparées à d'autres types de messages asynchrones (voir Section 52.2.7). Sinon, tout type de message autre que CopyData et CopyDone pourrait être traité comme terminant le mode copy-out.
Il existe un autre mode relatif à Copy appelé Copy-both. Il permet
un transfert de données en flot à grande vitesse vers
et à partir du serveur. Le mode Copy-both est
initié quand un processus serveur en mode walsender exécute une
instruction START_REPLICATION
. Le processus
serveur envoie un message CopyBothResponse au client. Le processus
serveur et le client peuvent ensuite envoyer des messages CopyData
jusqu'à ce que l'un des deux envoie un message CopyDone. Après que le client
ait envoyé un message CopyDone, la connexion se transforme en mode copy-out,
et le client ne peut plus envoyer des 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 côtés ont envoyé un message CopyDone, le mode copie est terminé
et le processus serveur retourne dans le mode de traitement des commandes.
Si une erreur est détectée par le serveur pendant le mode copy-both, le
processus serveur enverra un message ErrorResponse, ignorera les messages
du client jusqu'à réception d'un message Sync message, puis enverra un
message ReadyForQuery avant de continuer le traitement habituel. Le client
doit traiter la réception d'un ErrorResponse comme une fin de la copie dans
les deux sens ; aucun CopyDone ne doit être envoyé dans ce cas. Voir
Section 52.4 pour plus d'informations sur le
sous-protocole transmis pendant le mode copy-both.
Les messages CopyInResponse, CopyOutResponse et CopyBothResponse
incluent des champs qui informent le client du nombre de colonnes
par ligne et les codes de format utilisés par chaque colonne. (Avec
l'implémentation courante, toutes les colonnes d'une opération
COPY
donnée utiliseront le même format mais la
conception du message ne le suppose pas.)
Il existe plusieurs cas pour lesquels le serveur 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 ces messages à tout moment même si aucune requête n'est en cours. Vérifier ces cas avant de commencer à lire la réponse d'une requête est un minimum.
Il est possible que des messages NoticeResponse soient engendrés en dehors de toute activité ; par exemple, si l'administrateur de la base de données commande un arrêt « rapide » de la base de données, le serveur enverra un NoticeResponse l'indiquant avant de fermer la connexion. Les clients devraient toujours être prêts à accepter et afficher les messages NoticeResponse, même si la connexion est inactive.
Des messages ParameterStatus seront engendrés à chaque fois que la valeur
active d'un paramètre est modifiée, et cela pour tout paramètre que le
serveur pense utile au client. Cela survient plus généralement en réponse à
une commande SQL set
exécutée par le client. Ce cas est en fait
synchrone -- mais il est possible aussi que le changement de statut d'un
paramètre survienne à la suite d'une modification par l'administrateur des
fichiers de configuration ; changements suivis de l'envoi du signal
SIGHUP au postmaster. De plus, si une commande
SET
est annulée, un message ParameterStatus approprié
sera ajouté pour rapporter la valeur effective.
À ce jour, il existe un certain nombre de paramètres codés en dur pour
lesquels des messages ParameterStatus seront engendrés : on trouve
server_version
,
server_encoding
,
client_encoding
,
application_name
,
is_superuser
,
session_authorization
,
DateStyle
,
IntervalStyle
,
TimeZone
,
integer_datetimes
, et
standard_conforming_strings
.
(server_encoding
, TimeZone
et
integer_datetimes
n'ont pas été reportés par les
sorties avant la 8.0 ; standard_conforming_strings
n'a pas été reporté par les sorties avant la 8.1 ;
IntervalStyle
n'a pas été reporté par les sorties
avant la 8.4;
application_name
n'a pas été reporté par les sorties
avant la 9.0.). Notez que
server_version
,
server_encoding
et
integer_datetimes
sont des pseudo-paramètres qui ne peuvent pas
changer après le lancement.
Cet ensemble pourrait changer dans le futur, voire devenir configurable.
De toute façon, un client peut ignorer un message ParameterStatus
pour les paramètres qu'il ne comprend pas ou qui ne le concernent pas.
Si un client lance une commande listen
, alors le serveur
enverra un message NotificationResponse (à ne pas confondre avec
NoticeResponse !) à chaque fois qu'une commande
notify
est exécutée pour le canal de même nom.
Actuellement, NotificationResponse ne peut être envoyé qu'à l'extérieur d'une transaction. Il ne surviendra donc pas au milieu d'une réponse à une commande, mais il peut survenir juste avant ReadyForQuery. Il est toutefois déconseillé de concevoir un client en partant de ce principe. La bonne pratique est d'être capable d'accepter NotificationResponse à tout moment du protocole.
Pendant le traitement d'une requête, le client peut demander l'annulation de la requête. La demande d'annulation n'est pas envoyée directement au serveur par la connexion ouverte pour des raisons d'efficacité de l'implémentation : il n'est pas admissible que le serveur vérifie constamment les messages émanant du client lors du traitement des requêtes. Les demandes d'annulation sont relativement inhabituelles ; c'est pourquoi elles sont traitées de manière relativement simple afin d'éviter que ce traitement ne pénalise le fonctionnement normal.
Pour effectuer une demande d'annulation, le client ouvre une nouvelle connexion au serveur et envoie un message CancelRequest à la place du message StartupMessage envoyé habituellement à l'ouverture d'une connexion. Le serveur traitera cette requête et fermera la connexion. Pour des raisons de sécurité, aucune réponse directe n'est faite au message de requête d'annulation.
Un message CancelRequest sera ignoré sauf s'il contient la même donnée clé (PID et clé secrète) que celle passée au client lors du démarrage de la connexion. Si la donnée clé correspond, le traitement de la requête en cours est annulé (dans l'implantation existante, ceci est obtenu en envoyant un signal spécial au processus serveur qui traite la requête).
Le signal d'annulation peut ou non être suivi d'effet -- par exemple, s'il arrive après la fin du traitement de la requête par le serveur, il n'aura alors aucun effet. Si l'annulation est effective, il en résulte la fin précoce de la commande accompagnée d'un message d'erreur.
De tout ceci, il ressort que, pour des raisons de sécurité et d'efficacité, le client n'a aucun moyen de savoir si la demande d'annulation a abouti. Il continuera d'attendre que le serveur réponde à la requête. Effectuer une annulation permet simplement d'augmenter la probabilité de voir la requête en cours finir rapidement et échouer accompagnée d'un message d'erreur plutôt que réussir.
Comme la requête d'annulation est envoyée via une nouvelle connexion au serveur et non pas au travers du lien de communication client/serveur établi, il est possible que la requête d'annulation soit lancée par un processus quelconque, pas forcément celui du client pour lequel la requête doit être annulée. Cela peut fournir une flexibilité supplémentaire dans la construction d'applications multi-processus ; mais également une faille de sécurité puisque des personnes non autorisées pourraient tenter d'annuler des requêtes. La faille de sécurité est comblée par l'exigence d'une clé secrète, engendrée dynamiquement, pour toute requête d'annulation.
Lors de la procédure normale de fin le client envoie un message Terminate et ferme immédiatement la connexion. À la réception de ce message, le serveur ferme la connexion et s'arrête.
Dans de rares cas (tel un arrêt de la base de données par l'administrateur), le serveur peut se déconnecter sans demande du client. Dans de tels cas, le serveur tentera d'envoyer un message d'erreur ou d'avertissement en donnant la raison de la déconnexion avant de fermer la connexion.
D'autres scénarios de fin peuvent être dus à différents cas d'échecs, tels qu'un « core dump » côté client ou serveur, la perte du lien de communications, la perte de synchronisation des limites du message, etc. Que le client ou le serveur s'aperçoive d'une fermeture de la connexion, le buffer sera vidé et le processus terminé. Le client a la possibilité de lancer un nouveau processus serveur en recontactant le serveur s'il ne souhaite pas se finir. Il peut également envisager de clore la connexion si un type de message non reconnu est reçu ; en effet, ceci est probablement le résultat de la perte de synchronisation des limite de messages.
Que la fin soit normale ou non, toute transaction ouverte est annulée, non
pas validée. Si un client se déconnecte alors qu'une requête autre que
select
est en cours de traitement, le serveur terminera
probablement la requête avant de prendre connaissance de la déconnexion.
Si la requête est en dehors d'un bloc de transaction (séquence
begin
... commit
), il se peut que les résultats
soient validés avant que la connexion ne soit reconnue.
Si PostgreSQL a été compilé avec le support de SSL, les communications client/serveur peuvent être chiffrées en l'utilisant. Ce chiffrement assure la sécurité de la communication dans les environnements où des agresseurs pourraient capturer le trafic de la session. Pour plus d'informations sur le cryptage des sessions PostgreSQL avec SSL, voir Section 18.9.
Pour initier une connexion chiffrée par SSL, le client envoie
initialement un message SSLRequest à la place d'un StartupMessage. Le
serveur répond avec un seul octet contenant S
ou N
indiquant respectivement s'il souhaite ou non utiliser le SSL.
Le client peut alors clore la connexion s'il n'est pas satisfait de la
réponse. Pour continuer après un S
, il faut échanger une poignée
de main SSL (handshake) (non décrite ici car faisant partie de
la spécification SSL) avec le serveur. En cas de succès, le
StartupMessage habituel est envoyé. Dans ce cas, StartupMessage et toutes
les données suivantes seront chiffrées avec SSL. Pour continuer
après un N
, il suffit d'envoyer le StartupMessage habituel et
de continuer sans chiffrage.
(Il est aussi autorisé de lancer un message GSSENCRequest après une
réponse N
pour essayer d'utiliser le chiffrement de
GSSAPI au lieu de SSL.)
Le client doit être préparé à gérer une réponse ErrorMessage à un SSLRequest émanant du serveur. Ceci ne peut survenir que si le serveur ne dispose pas du support de SSL. (De tels serveurs sont maintenant très anciens, et ne doivent certainement plus exister.) Dans ce cas, la connexion doit être fermée, mais le client peut choisir d'ouvrir une nouvelle connexion et procéder sans SSL.
Quand le chiffrement SSL doit être réalisé, le serveur
doit envoyer seulement l'octet S
, puis attendre que le
client initie une poignée de main SSL. Si des octets
supplémentaires sont disponibles en lecture à ce moment, cela pourrait
signifier qu'une attaque man-in-the-middle
tente de réaliser une attaque
buffer-stuffing
(CVE-2021-23222).
Les clients doivent être codés pour soit lire exactement un octet du
socket avant de rendre le socket à la bibliothèque SSL, soit traiter
comme une violation de protocole tout octet supplémentaire.
Un SSLRequest initial peut également être utilisé dans une connexion en cours d'ouverture pour envoyer un message CancelRequest.
Alors que le protocole lui-même ne fournit pas au serveur de moyen de forcer le chiffrage SSL, l'administrateur peut configurer le serveur pour rejeter les sessions non chiffrées, ce qui est une autre façon de vérifier l'authentification.
Si PostgreSQL a été compilé avec le support de GSSAPI, les communications client/serveur peuvent être chiffrées en utilisant GSSAPI. Cela fournit une sécurité des communications dans des environnements où des attaquants pourraient être capables de capturer le trafic de session. Pour plus d'information sur le chiffrement des sessions PostgreSQL avec GSSAPI, voir Section 18.10.
Pour initier une connexion chiffrée avec GSSAPI,
le client envoie initialement un message GSSENCRequest plutôt
qu'un StartupMessage. Le serveur répond alors avec un seul octet
contenant G
ou N
, indiquant
respectivement qu'il est d'accord ou non pour mettre en oeuvre
un chiffrement GSSAPI.
Le client peut à ce moment fermer la connexion s'il n'est pas satisfait
de la réponse. Pour continuer après G
,
en utilisant les appels en C de GSSAPI tel que mentionnés dans la
RFC2744 ou équivalent, il faut faire une initialisation GSSAPI
en appelant gss_init_sec_context()
en boucle, avec un
premier paramètre vide, puis avec le résultat du serveur, jusqu'à ce que
ce dernier ne renvoie plus de résultat. L'envoi des résultats de
gss_init_sec_context()
au serveur doit être préfixé
par la longueur du message, exprimée en un entier de quatre octets dans
l'ordre du réseau. Pour continuer après la réception du
N
, envoyer le StartupMessage usuel et continuer sans
chiffrement. (Il est aussi possible de lancer un message SSLRequest après
une réponse N
pour essayer d'utiliser le chiffrement
SSL au lieu de celui de GSSAPI.)
La partie cliente doit aussi être prête à gérer une réponse ErrorMessage du serveur à GSSENCRequest. Elle peut se produire uniquement si le serveur date d'avant le support du chiffrement GSSAPI dans PostgreSQL. Dans ce cas, la connexion doit être fermée, mais le client peut choisir d'ouvrir une nouvelle connexion et continuer sans demander le chiffrement GSSAPI.
Quand le chiffrement GSSAPI peut être réalisé, le serveur
doit envoyer seulement l'octet G
, puis attendre que le
client initie une poignée de main GSSAPI. Si des octets
supplémentaires sont disponibles en lecture à ce moment, cela pourrait
signifier qu'une attaque man-in-the-middle
tente de réaliser une attaque
buffer-stuffing
(CVE-2021-23222).
Les clients doivent être codés pour soit lire exactement un octet du
socket avant de rendre le socket à la bibliothèque GSSAPI, soit traiter
comme une violation de protocole tout octet supplémentaire.
Un GSSENCRequest initial peut aussi être utilisé dans une connexion en train d'être ouverte pour envoyer un message CancelRequest.
Une fois que le chiffrement GSSAPI a été mise en place
avec succès, utilisez gss_wrap()
pour chiffrer le
StartupMessage normal et toutes les données qui suivent, toujours
préfixées par leur longueur exprimée en entier de quatre octets dans
l'ordre du réseau. Il est à noter que le serveur acceptera uniquement du
client des paquets chiffrés dont la longueur est inférieure à
16 ko ; gss_wrap_size_limit()
devrait être
utilisée par le client pour déterminer quelle taille de message non
chiffré n'excède pas cette limite, et découper les messages plus grands
pour les passer en plusieurs appels à gss_wrap()
. La
taille typique des segments non chiffrées est de 8 ko, donnant des
paquets chiffrés légèrement plus grands que 8 ko mais bien en-dessous
du maximum de 16 ko. Le serveur n'est pas censé envoyer des paquets
chiffrés plus grands que 16 ko au client.
Bien que le protocole lui-même n'offre pas de moyen pour le serveur de forcer le chiffrement GSSAPI, l'administrateur peut configurer le serveur pour rejeter les sessions non chiffrées via le contrôle d'authentification.