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 normale, 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.
Enfin, notez que toutes les requĂȘtes du message Query auront la mĂȘme valeur
pour la fonction statement_timestamp()
, car cet
horodatage est mis à jour seulement lors de la réception d'un message
Query. De ce fait, ils auront aussi tous la mĂȘme valeur pour
transaction_timestamp()
, sauf dans les cas oĂč la
chaĂźne de requĂȘtes finit avec une transaction prĂ©cĂ©demment dĂ©marrĂ©e et
en commence une nouvelle.
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
(« fermer » 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Ă©es 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 toutes les donnĂ©es 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.