9.1第46章

From PostgreSQL wiki
Jump to navigationJump to search

Postgres 使用一种基于消息的协议用于前端和后端(服务器和客户机)之间通讯。 该协议是在 TCP/IP 和 Unix 域套接字上实现的。 端口号 5432 已经在 IANA 注册为使用这种协议的常用端口,但实际上任何非特权端口号都可以使用。

这份文档描述了版本 3.0 的协议,在 Postgres 版本 7.4 和以后的版本中实现。对于以前的协议的描述,请参考以前版本的 PostgreSQL 文档。一个服务器可以支持多种协议版本。初始化的启动信息告诉服务器客户端可以接受的协议版本,然后服务器则遵守该版本的协议——如果它能做到的话。

在这个协议的基础上建立的更高级特性(例如,libpq 是如何在建立联接以后传递某种环境变量的)在其他地方描述.

为了可以有效地为多个客户端提供服务,服务器为每个客户端派生一个新的"后端"进程。 在目前的实现里,在检测到到来的连接请求后,马上创建一个新的子进程。 不过,这些是对协议透明的。对于协议而言,术语"后端"和"服务器"是可以互换的; 类似的还有"前端"和"客户机"也是可以互换的。 43.1. 概述

协议在启动和正常操作过程中有不同的阶段。 在启动阶段里,前端打开一个到服务器的连接并且认证自身以满足服务器。 (这些可能包含一条消息,也可能包含多条消息,根据使用的认证方法而不同。) 如果所有这些事情都运行平稳,那么服务器就发送状态信息给前端,并最后进入正常操作。 除了初始化的启动请求之外,这部分协议是服务器驱动的。

在正常操作中,前端发送查询和其它命令到后端,然后后端返回查询结果和其它响应。 有少数几种情况(比如 NOTIFY)是后端发送主动提供的消息,但这部分的绝大多数情况都是由前端请求驱动的。

会话的终止通常是由前端来选择的,但是也可以在某些情况下由后端强制执行。 不管那种情况,如果后端关闭连接,那么他将在退出之前回滚(完成)所有打开的(未完成的)事务。

在正常操作中,SQL 命令可以通过两个子协议中的任何一个执行。 在"简单查询"协议中,前端只是发送一个文本查询串, 然后后端马上分析并执行它。在"扩展查询"协议中, 查询的处理被分割为多个步骤:分析,参数值绑定,和执行。 这样就可以提供灵活性和性能的改进,代价是额外的复杂性。

正常操作有用于类似 COPY 这样的额外的子协议。 43.1.1. 消息概貌

所有通讯都是通过一个消息流进行的。消息的第一个字节标识消息类型, 然后后面跟着的四个字节声明消息剩下部分的长度(这个长度包括长度域自身,但是不包括消息类型字节)。 剩下的消息内容由消息类型决定。由于历史原因,客户端发送的最早的消息(启动消息)没有初始的消息类型字节。

为了避免和消息流丢失同步信息,服务器和客户端通常都是把整个消息读取到一个缓冲区里(使用字节计数), 然后才试图处理其内容。这样我们在处理内容的过程中,如果发现错误,就比较容易恢复。 在非常罕见的情况下(比如说没有足够的信息缓冲消息),接收端可以使用字节计数来判断它在重新读取信息之前需要忽略多少输入。

通常,服务器和客户端都需要注意决不发送一条不完整的消息。 这些通常是通过在发送整条信息之前,在一个缓冲区里整理整条消息。 如果在发送或者接受一条消息的中间发生了通讯错误,那么唯一合理的反应是放弃连接,因为恢复消息边界同步的可能性很小。 43.1.2. 扩展查询概述

在扩展查询协议中,SQL 命令的执行是分割成多个步骤的。 步骤与步骤之间保存的状态是由两类的对象代表的: 准备好的语句(prepared statements)和入口(portals)。 一个准备好的语句代表一个文本查询字串的经过分析,语意解析,以及规划之后的结果。 一个准备好的语句不一定就是可以执行的,因为它可能还缺乏 参数的值。 一个入口代表一个已经可以执行的或者已经部分执行过的语句,所有参数都已经填充到位了。 (对于SELECT语句,入口等效于一个打开的游标, 我们使用不同的术语是因为游标不能处理非SELECT语句。)

完整的执行周期包括一个分析步骤, 它从一个文本的查询字串里创建一个准备好的语句; 一个绑定(bind)步骤, 它用一个准备好的语句和任何所需要的参数值创建一个入口;以及一个 执行(execute)步骤,它运行一个入口的查询。 如果是一个返回数据行的查询(SELECT,SHOW 等), 系统可以告诉执行步骤只抓取有限的一些行,这样就可能需要多个执行步骤来完成操作。

后端可以跟踪多个准备好的语句和入口(但是要注意,这些只在一个会话内部存在,从来不能在会话之间共享)。 现存的准备好地语句和入口都是用创建它们的时候赋予的名字引用的。 另外,还存在一个"未命名(unnamed)" 的准备好语句和入口。 尽管它们的行为和命名对象大部分相同,但是它们是针对只执行一次然后就抛弃的查询进行优化过的, 而在命名对象上的操作是针对多次使用优化的。 43.1.3. 格式和格式代码

特定数据类型的数据可以用几种不同的格式中的任意一种来传递。 到 PostgreSQL 7.4 的时候,只支持"文本"和"二进制"两种格式, 但是协议为未来的扩展提供了的手段。任意值要求的格式是用一个格式代码声明的。 客户端可以为每个传输的参数值和查询结果的每个字段声明一个格式代码。 文本的格式代码是零,二进制的格式代码是一,所有其它的格式代码都保留给将来定义。

文本方式的数值是特定数据类型的输入/输出转换函数接受或者生成的数值的字串形式。 在传输表现上,字串末尾没有空字符;如果前端要想把收到的值当作 C 字串处理,那么必须自己加上一个。 (另外,文本格式不允许嵌入的空。)

整数的二进制表现形式采用网络字节序(高位在前)。对于其它数据类型, 情参考文档或者源代码获取其二进制表现形式的信息。请注意,复杂的数据类型的二进制形式可能在不同服务器版本之间变化; 文本格式通常是最具有移植性的选择。


43.2. 消息流

本节描述消息流。以及每种消息类型的语意。(每种信息的准确表现细节在 Section 43.4 里。)因连接的状态不同,存在几种不同的子协议:启动(startup),查询(query),函数调用(function call)和 COPY 和结束(termination)。还有用于通知响应和命令取消的特殊信息,这些特殊信息可能在启动阶段过后的任何时间产生。 43.2.1. 启动

要开始一个会话,前端打开一个与服务器的连接并且发送一个启动消息。 这个消息包括用户名以及用户希望与之连接的数据库; 它还标识要使用的特定的协议版本。(另外,启动信息可以包括用于运行时参数的额外设置。) 服务器然后就使用这些信息以及它的配置文件的内容 (比如 pg_hba.conf)以判断这个连接是否可以接受,以及需要什么样的额外的认证(如果需要)。

然后服务器就发送合适的认证请求信息,前端必须用合适的认证响应信息来响应(比如一个口令)。 理论上讲,这样的认证请求/响应循环可能需要多次迭代,但是目前的认证方法都不需要超过一次的请求和响应。 有些方法则根本不需要前端的响应,因此就没有认证请求发生。

认证周期的结束要么是以服务器的拒绝连接(ErrorResponse), 要么是以 AuthenticationOK 结束的。

这个阶段来自服务器可能消息是:

ErrorResponse

   连接请求被拒绝。 然后服务器马上关闭联接。

AuthenticationOk

   认证交换成功完成.

AuthenticationKerberosV5

   现在前端必须与服务器进行一次 Kerberos V5 认证对话(在这里没有描述,Kerberos 规范的一部分)。 如果对话成功,服务器响应一个 AuthenticationOk (认证成功)信息, 否则它响应一个 ErrorResponse (错误响应)。

AuthenticationCleartextPassword

   现在前端必须明文形式发送一个包含口令的 PasswordMessage (未加密口令)包。 如果这是正确的口令,服务器用一个 AuthenticationOk 包响应,否则它响应一个 ErrorResponse 包。

AuthenticationCryptPassword

   现在前端必须发送一个 PasswordMessage 包,该包包含用 crypt(3) 加密的口令,加密时使用了在 AuthenticationCryptPassword 消息里声明的 2 字节盐粒。如果这是正确口令,服务器用一个 AuthenticationOk 响应,否则它用一个 ErrorResponse 响应。

AuthenticationMD5Password

   现在前端必须发送一个包含用 MD5 加密的口令的 PasswordMessage, 加密时使用了在 AuthenticationMD5Password 消息里声明的 4 字符盐粒。 如果这是正确口令,服务器用一个 AuthenticationOk 响应,否则它用一个 ErrorResponse 响应。 

AuthenticationSCMCredential

   这个响应只对那些支持 SCM 信任消息的本地 Unix 域连接出现。 前端必须发出一条 SCM 信任消息然后发送一个数据字节。 (数据字节的内容并不会被注意; 它的作用只是确保服务器等待了足够长的时间来接受信任信息。)如果信任是可以接受的, 那么服务器用 AuthenticationOk 响应,否则用 ErrorResponse 响应。 

如果前端不支持服务器要求的认证方式,那么它应该马上关闭联接。

在收到 AuthenticationOk 包之后,前端必须等待来自后端的更多消息。在这个阶段会启动一个后端进程,而前端只是一个感兴趣的看热闹的。启动尝试仍然有可能失败(ErrorResponse),但是通常情况下,后端将发送一些 ParameterStatus 消息,BackendKeyData,以及最后 ReadyForQuery。

在这个阶段,后端将尝试应用任何在启动消息里给出额外的运行时参数设置。 如果成功,这些值将成为会话缺省。错误将导致 ErrorResponse 并退出。

这个阶段来自后端的可能消息是:

BackendKeyData

   这个消息提供了密钥(secret-key)数据, 前端如果想要在稍后发出取消的请求, 则必须保存这个数据。前端不应该响应这个信息, 但是应该继续侦听等待 ReadyForQuery 消息。

ParameterStatus

   这个消息告诉前端有关后端参数的当前(初始化)设置,比如 client_encoding 或者 DateStyle 等。前端可以忽略这些信息,或者记录其设置用于将来使用; 参阅 Section 43.2.6 获取更多细节。前端不应该响应这些信息, 而是应该继续侦听 ReadyForQuery 消息。 

ReadyForQuery

   后端启动成功,前端现在可以发出命令.

ErrorResponse

   后端启动失败.在发送完这个消息之后联接被关闭.

NoticeResponse

   发出了一个警告信息。前端应该显示这个信息, 并且继续等待 ReadyForQuery 或 ErrorResponse。

后端在每个查询循环后都会发出一个相同的 ReadyForQuery 消息。前端可以合理地认为 ReadyForQuery 是一个查询循环的开始(而 BackendKeyData 表明启动阶段的成功完成),或者认为 ReadyForQuery 是启动阶段和每个随后查询循环的结束,具体是那种情况取决于前端的编码需要。 43.2.2. 简单查询

一个查询循环是由前端发送一条 Query 消息给后端进行初始化的。这条消息包含一个用文本字串表达的 SQL 命令(或者一些命令)。后端根据查询命令字串的内容发送一条或者更多条响应消息给前端,并且最后是一条 ReadyForQuery 响应信息。 ReadyForQuery 通知前端它可以安全地发送新命令了。(实际上前端不必在发送其它命令之前等待 ReadyForQuery,但是这样一来,前端必须负责区分早先发出的命令失败,而稍后发出的命令成功的情况。)

从后端来的可能的消息是:

CommandComplete

   一个 SQL 命令正常结束.

CopyInResponse

   后端已经准备好从前端拷贝数据到一个表里面去。 又见 Section 43.2.5。

CopyOutResponse

   后端已经准备好从一个表里拷贝数据到前端里面去。 又见 Section 43.2.5。

RowDescription

   表示为了响应一个 SELECT,FETCH,等的查询, 将要返回一个行。这条消息的内容描述了这行的字段布局。这条消息后面将跟着每个返回给前端的行一个的 DataRow 消息。 

DataRow

   SELECT,FETCH,等查询返回的结果集中的一行。 

EmptyQueryResponse

   识别了一个空的查询字串.

ErrorResponse

   出错了。

ReadyForQuery

   查询字串的处理完成。 发送一个独立的消息来标识这个是因为查询字串可能包含多个 SQL 命令。 (CommandComplete 只是标记一条 SQL 命令处理完毕,而不是整个字串。) ReadyForQuery 总会被发送,不管是处理成功结束还是产生错误。

NoticeResponse

   发送了一个与查询有关的警告信息。 注意信息是附加在其他响应上的,也就是说, 后端将继续处理该命令。

SELECT (或其它返回结果集的查询,比如 EXPLAIN 或 SHOW) 查询的响应信息通常包含 RowDescription,零个或者多个 DataRow 消息,以及最后的 CommandComplete。 从前端来回的 COPY 调用Section 43.2.5里提到的特殊的协议。 所有其他查询类型通常只生成一个 CommandComplete 消息。

因为查询字串可能包含若干个查询(用分号分隔), 所以在后端完成查询字串的处理之前可能有好几个这样的响应序列。 如果整个字串已经处理完,后端已经准备好接受新查询字串的时候则发出 ReadyForQuery 消息。

如果收到一个完全空(除了空白之外没有内容)的查询字串, 那么响应是一条 EmptyQueryResponse 后面跟着 ReadyForQuery。

在出现错误的时候,发出一个 ErrorResponse 消息,后面跟着 ReadyForQuery。 查询字串的所有后继的处理都被 ErrorResponse 中止(即使里面还有查询也这么干)。 请注意这些事情可能在处理一个查询产生的消息序列的中途发生。

在简单查询模式,检索出来的数值的格式总是文本的, 除非给出的命令是一个从一个声明了 BINARY 选项的游标上 FETCH。 在这种情况下,检索出来的数值是二进制格式的。在 RowDescription 消息里给出的格式代码告诉我们用了那种格式。

前端在等待其他类型的消息时必须准备接收 ErrorResponse 和 NoticeResponse 消息。参阅 Section 43.2.6 后端因为外部的时间可能生成的消息。

我们建议的方法是把前端代码写成状态机的风格,它可以在任何时刻接受任何有意义的信息,而不是写成假设消息的准确序列的代码. 43.2.3. 扩展查询

扩展的查询协议把上面描述的简单协议分裂成若干个步骤。 准备的步骤可以多次复用以提高效率。另外,还可以获得额外的特性, 比如我们把数据值作为独立的参数提供的可能性,而不是把它们直接插入一个查询字串。

在扩展的协议里,前端首先发送一个 Parse 消息,它包含一个文本查询字串, 另外还有一些有关参数占位符的数据类型的信息,以及一个最终准备好的语句对象的名字(一个空字串选择未命名的准备好的语句)。 响应要么是一个 ParseComplete 要么是 ErrorResponse。 参数数据类型可以用 OID 来声明;如果没有给出, 那么分析器将试图用它对付无类型的字串常量的方法来推导其数据类型。

   注意: 一个参数数据类型可以通过设置为零, 或者让参数类型 OID 的数目比查询字串里的参数符号 ($n) 的数目少的方法不予声明。 另外一个特例是参数的类型可以声明为void(也就是说,伪类型 oid 的 OID)。 这是为了允许用于某些函数参数的参数符号实际上是 OUT 参数。 通常情况下,没有什么环境会用到 void 参数, 但是如果在函数的参数列表里出现了这么一个参数符号,那么它实际上会被忽略。 比如,一个像这样的函数调用:foo($1,$2,$3,$4),如果 $3 和 $4 声明为类型是 void,那么这个函数调用可以匹配一个带有两个 IN 和两个 OUT 参数的函数。 
   注意: 在一个 Parse 消息里包含的查询字串不能包含超过一个 SQL 语句; 否则就会报告一个语法错误。这个限制在简单查询协议中并不存在, 但是它存在于扩展的协议中,因为允许准备好的语句或者入口包含多个命令将导致协议过度地复杂。 

对有名准备好语句对象的查询规划发生在收到 Parse 消息的时候。 如果一个查询会带着不同参数重复执行,那么发送单个带着参数化的查询的 Parse 消息, 后面跟着多个 Bind 和 Execute 消息会很有帮助。这样将避免在每次执行的时候重新规划该查询。

如果 Parse 消息没有定义参数,那么无名的准备好语句很可能自于 Parse 处理的时候规划。 但是如果有参数,那么查询规划就会推迟到该语句的第一个 Bind 消息。 规划器在对该查询进行规划的时候将考虑在 Bind 消息里提供的实际参数值。

   注意: 为一个参数化了的查询生成的查询规划可能比用实际参数替换后的查询生成的查询规划效率差些。 在给一个赋予了有名准备好语句对象的参数化查询进行规划的时候, 查询规划器无法基于实际参数值做决策(比如,索引选择性)。 在使用无名语句的时候避免了这个开销,因为直到参数值可用的时候才进行规划。
   如果收到引用无名的准备好语句的第二个或者是以后的 Bind 消息同时没有干涉的 Parse 消息, 那么这个查询就不会被规划。在第一个 Bind 消息里使用的参数值可能生成一个只对所有可能的参数值中的一小部分有效的规划。 要为一套信的参数集强制该查询的重新规划,要发送另外一个 Parse 把替换该无名准备好语句对象替换掉。 

如果成功创建了一个命名的准备好语句对象,那么它将持续到当前会话结束, 除非明确删除。一个未命名的准备好语句只持续到下一个声明未命名的语句为目标的 Parse 语句发出为止。 (请注意一个简单的查询消息也删除未命名语句。)命名的语句必须明确地关闭,然后才能用一个 Parse 消息重新定义, 但是未命名的语句并不要求这个动作。命名的准备好语句也可以在 SQL 命令级创建和访问, 方法是使用 PREPARE 和 EXECUTE。

只要准备好的语句还存在,那么我们就可以使用 Bind 消息很容易地使之进入执行状态。 Bind 消息给出源准备好语句的名字(空字串表示未命名的准备好语句), 目标入口的名字(空字串表示未命名的入口),以及用于那些在准备好的语句中出现的所有参数占位符的数值。 提供的参数集必须匹配那些准备好的语句需要的东西。 (如果你在 Parse 消息里声明任何 void 参数,那么在 Bind 消息里给它们传递 NULL 值。) Bind 还声明用于查询返回的任何数据的格式; 格式可以一次声明,也可以每个字段进行声明。响应要么是 BindComplete 要么是 ErrorResponse。

   注意: 输出的格式是文本还是二进制是由 Bind 里给出的格式代码决定的, 不管用的是什么 SQL 命令。在使用扩展的查询协议的时候,游标声明里的 BINARY 属性是无关的。 

如果成功创建了一个入口对象,那么它将持续到当前事务的结尾, 除非明确删除。一个未命名的入口在事务的结尾删除,或者是在发出的下一个 Bind 语句声明了一个未命名的入口作为目标为止。 (请注意一个简单查询效益也会删除这个未命名的入口。)命名的入口在可以用一个 Bind 消息重新定义之前必须明确关闭, 但是未命名的入口不要求这个动作。命名的入口也可以在 SQL 创建和访问, 方法是使用 DECLARE CURSOR 和 FETCH。

只要存在一个入口,那么就可以用一个 Execute 消息执行它。 Execute 消息声明入口的名字(空字串表示未命名入口)和一个最大的结果行计数(零表示"抓取所有行")。 结果行计数只对包含返回结果集的入口有意义;在其它情况下,该命令总是执行到结束, 而行计数会被忽略。Execute 可能的响应和那些通过简单查询协议发出的查询一样, 只不过 Execute 不会导致后端发出 ReadyForQuery 或者 RowDescription。

如果 Execute 在一个入口的执行完成之前终止(因为达到了一个非零的结果行计数), 它将发送一个 PortalSuspended 消息;这个消息的出现告诉前端应该在同一个入口上发出另外一个 Execute 以完成操作。 在入口的执行完成之前,不会发出表示源 SQL 命令结束的 CommandComplete 消息。 因此 Execute 阶段总是由下列消息之一出现标志着结束的: CommandComplete,EmptyQueryResponse(如果入口是从一个空字串创建出来的),ErrorResponse,或者 PortalSuspended。

每个扩展查询消息序列完成后,前端都应该发出一条 Sync 消息。 这个无参数的消息导致后端关闭当前事务——如果当前事务不是在一个BEGIN/COMMIT 事务块中的话("关闭"的意思就是在没有错误的情况下提交, 或者是有错误的情况下回滚)。然后响应一条 ReadyForQuery 消息。 Sync 的目的是提供一个错误恢复的重新同步的点。 如果在处理任何扩展查询消息的时候侦测到任何错误,那么后端发出 ErrorResponse, 然后读取并抛弃消息,直到一个 sync 的到来,然后发出 ReadyForQuery 并且返回到正常的消息处理中。 (但是要注意如果正在处理 Sync 的时候发生了错误,那么不会忽略任何东西 — 这样就保证了为每个 Sync 发出一个并且只是一个的 ReadyForQuery。)

   注意: Sync 并不导致一个用 BEGIN 打开的事务块关闭。 我们可以侦测到这种情况,因为 ReadyForQuery 消息包含事务状态信息。 

除了这些基本的,必须的操作之外,在扩展查询协议里还有几种可选的操作可以使用。

Describe 消息(入口变体)声明一个现有的入口的名字(或者一个未命名入口就是空字串)。 响应是一个 RowDescription 消息,它描述了执行入口将要返回的行; 或者是一个 NoData 消息——如果入口并不包含会返回行的查询;或者是一个 ErrorResponse——如果没有这个入口。

Describe 消息(语句变体)声明一个现有的准备好语句的名字(如果是一个未命名的准备好语句则是一个空字串)。 响应是一个描述该语句需要的参数的 ParameterDescription 消息, 后面跟着一个描述语句最终执行后返回的行的 RowDescription 消息(或者是 NoData 消息,如果该语句不返回行)。 如果没有这样的准备好语句,则返回 ErrorResponse。请注意因为还没有发出 Bind, 所以后端还不知道用于返回字段的格式;在这种情况下,RowDescription 消息里面的格式代码字段将是零。

   提示: 在大多数情况下,前端在发出 Execute 之前应该发出某种 Describe 的变体, 以保证它知道如何解析它将得到的结果。 

Close 消息关闭一个现有的准备好的语句或者入口,并且释放资源。 对一个不存在的语句或者入口名字发出 Close 不是一个错误。 响应通常是 CloseComplete,但如果在释放资源的时候发生了一些困难也可以是 ErrorResponse。 请注意关闭一个准备好地语句隐含地关闭任何从该语句构造出来地打开的入口。

Flush 消息并不导致任何特定的输出的生成, 但是强制后端发送任何还在它的输出缓冲区中停留的数据。 Flush 必须在除 Sync 外的任何扩展查询命令后面发出——如果前端希望在发出更多的命令之前检查该命令的结果的话。 如果不 Flush,后端返回的消息将组合成最小可能的数据包个数,以减少网络负荷。

   注意: 简单查询消息大概等于一系列使用未命名的准备好的语句和无参数的入口对象的 Parse,Bind,入口 Describe,Execute,Close,Sync。 一个区别是它会在查询字串中接受多个 SQL 语句,并在会话中为每个语句自动执行绑定/描述/执行序列。 另外一个区别是它不会返回 ParseComplete,Bindcomplete,CloseComplete,或者 NoData 消息。 

43.2.4. 函数调用

Function Call 子协议允许客户端请求一个对存在于数据库 pg_proc 系统表中的任意函数的直接调用。客户端必须在该函数上有执行的权限。

   注意: Function Call 子协议是一个遗留的特性,在新代码里可能最好避免用它。 类似的结果可以通过设置一个 SELECT function($1, ...) 的准备语句获得。 这样 Function Call 循环就可以用 Bind/Execute 代替。 

一个函数调用循环是由前端向后端发送一条 FunctionCall 消息初始化的。 然后后端根据函数调用的结果发送一条或者更多响应消息, 并且在最后是一条 ReadyForQuery 响应消息。 ReadyForQuery 通知前端它可以安全地发送一条新的查询或者函数调用了。

从后端来的可能的响应信息是:

ErrorResponse

   发生了一个错误。

FunctionCallResponse

   函数调用完成并且在消息中返回一个结果。 (请注意 Function Call 协议只能处理单个标量结果,不能处理 rowtype 或者结果集。)

ReadyForQuery

   函数调用处理完成。ReadyForQuery 将总是被发送,不管是成功完成处理还是发生一个错误。

NoticeResponse

   发出了一条有关该函数调用的警告信息。 通知是附加在其他响应上的,也就是说,后端将继续处理命令。

43.2.5. COPY 操作

COPY 命令允许在服务器和客户端之间高速的大批量数据传输。 拷贝入和拷贝出操作每个都把连接切换到一个独立的子协议中,并且持续到操作结束。

拷贝入模式(数据传输到服务器)是在后端执行一个 COPY FROM STDIN SQL 语句的时候初始化的。 后端发送一个 CopyInResponse 消息给前端。前端应该发送零条或者更多 CopyData 消息,形成一个输出数据的流。 (消息的边界和行的边界没有任何相关性要求,尽管通常那就是合理的边界选择。) 前端可以通过发送一个 CopyDone 消息来终止拷贝入操作(允许成功终止),也可以发出一个 CopyFail 消息(它将导致 COPY SQL 语句带着错误失败)。 然后后端就恢复回它在 COPY 开始之前的命令处理模式,可能是简单查询协议, 也可能是扩展查询协议。然后它会发送 CommandComplete (如果成功)或者 ErrorResponse (如果失败)。

如果在拷贝入模式下后端检测到了错误(包括接受接收到 CopyFiail 消息, 或者是任何除了 CopyData 或者 CopyDone 之外的前端消息), 那么后端将发出一个 ErrorResponse 消息。如果 COPY 命令是通过一个扩展的查询消息发出的, 那么后端从现在开始将抛弃前端消息,直到一个 Sync 消息到大,然后它将发出 ReadyForQuery 并且返回到正常的处理中。 如果 COPY 命令是在一个简单查询消息里发出的,那么该消息剩余部分被丢弃然后发出 ReadyForQuery 消息。 不管是哪种情况,任何前端发出的 CopyData,CopyDone,或者 CopyFail 消息都将被简单地抛弃。

拷贝出模式(数据从服务器发出)是在后端执行一个 COPY TO STDOUT SQL 语句的时候初始化的。 后端发出一个 CopyOutResponse 消息给前端,后面跟着零或者多个 CopyData 消息(总是每行一个), 然后跟着 CopyDone。然后后端回退到它在 COPY 开始之前的命令处理模式,然后发送 CommandComplete。 前端不能退出传输(除非是关闭连接或者发出一个 Cancel 请求),但是它可以抛弃不需要的 CopyData 和 CopyDone 消息。

在拷贝出模式中,如果后端检测到错误,那么它将发出一个 ErrorResponse 消息并且回到正常的处理。 前端应该把收到 ErrorResponse(或者实际上除了 CopyData 或者 CopyDone 之外的任何消息类型)当作终止拷贝出模式的标志。

CopyInResponse 和 CopyOutResponse 消息包括告诉前端每行的字段数以及每个字段使用的格式代码的信息。 (就目前的实现而言,某个 COPY 操作的所有字段都使用同样的格式,但是消息设计并不做这个假设。) 43.2.6. 异步操作

有几种情况下后端会发送一些并非由特定的前端的命令流提示的消息。 在任何时候前端都必须准备处理这些信息,即使是并未涉及到查询的处理的时候。 至少,我们应该在开始读取查询响应之前检查这些情况。

NoticeResponse 消息有可能是因为外部的活动而生成的; 比如,如果数据库管理员进行一次"快速"数据库关闭, 那么后端将在关闭连接之前发送一个 NoticeResponse 来表明这些。 因此,前端应该总是准备接受和显示 NoticeResponse 消息,即使连接通常是空闲的时候也如此。

如果后端认为前端应该知道的任何参数的活跃数值发生了变化, 那么都会产生 ParameterStatus 消息。这些最常见发生的地方是前端执行的一个 SET SQL 命令的响应, 并且这个时候实际上是同步 — 但是也有可能是数据库管理员改变了配置文件然后 SIGHUP 了 postmaster 导致的参数状态的变化。 同样,如果一个 SET 命令回滚,那么也会生成合适的 ParameterStatus 消息以报告当前有效的数值。

目前,系统内有一套会生成 ParameterStatus 消息的写成硬代码的参数: 他们是 server_version, server_encoding, client_encoding, is_superuser, session_authorization, DateStyle, TimeZone, integer_datetimes 和 standard_conforming_strings。 (server_encoding,TimeZone 和 integer_datetimes 在 8.0 版本之前没有报告。standard_conforming_strings 在版本 8.1 之前没有报告。) 请注意 server_version, server_encoding 和 integer_datetimes 是伪参数,启动后不能修改。 这些可能在将来改变,或者甚至是变成可以配置的。 因此,前端应该简单地忽略那些它不懂或者不关心的 ParameterStatus。

如果前端发出一个 LISTEN 命令, 那么后端将在为同一个通知名执行了 NOTIFY 命令后发送一个 NotificationResponse 消息(不要和 NoticeResponse 混了!)。

   注意: 目前,NotificationResponse 只能在一个事务外面发送,因此它将不会在一个命令响应序列中间出现, 但是它可能在 ReadyForQuery 之前出现。不过,在前端逻辑中做上述假设是不明智的。好的做法是在协议的任何点上都可以接受 NotificationResponse。 

43.2.7. 取消正在处理的请求

在一条查询正在处理的时候,可能取消该查询的处理。这样的取消请求不是直接通过打开的连接发送给后端的,这么做是因为实现的有效性:我们不希望后端在处理查询的过程中不停地检查前端来的输入。取消请求应该相对而言比较少见,所以我们把取消做得稍微笨拙一些,以便不影响正常状况的性能。

要发送一条取消请求,前端打开一个与服务器的新连接并且发送一条 CancelRequest 消息,而不是通常在新连接中经常发送的 StartupPacket 消息。服务器将处理这个请求然后关闭连接。出于安全原因,对取消请求消息不做直接的响应。

除非 CancelRequest 消息包含与连接启动过程中传递给前端的相同的键数据(PID 和 安全键字),否则它将被忽略。如果该请求匹配当前运行着的后端的 PID 和安全键字,则退出当前查询的处理(目前的实现里采用的方法是向正在处理该查询的后端进程发送一个特殊的信号。)

取消信号可能有也可能没有做用 — 例如,如果它在后端完成查询的处理后到达,那么它就没有做用。如果取消起作用了,其结果是当前命令带着一个错误信息提前退出。

这么做是对安全和有效性通盘考虑的结果,前端没有直接的方法获知一个取消请求是否成功。它必须继续等待后端对查询响应。执行取消仅仅是增加了当前查询快些结束的可能性,以及增加了当前查询会带着一条错误信息失败而不是成功执行的可能性。

因为取消请求是通过新的联接发送给服务器而不是通过平常的前端/后端通讯链接,所以取消请求可能是任意进程执行的,而不仅仅是要取消查询的前端。这样可能对创建多进程应用有某种灵活性的好处。但是同时这样也带来了安全风险,因为这样任何一个非认证用户都可能试图取消查询。这个安全风险通过要求在取消请求中提供一个动态生成的安全键字排除。 43.2.8. 终止

通常的终止过程的优雅方法是前端发送一条 Terminate (终止)消息并且立刻关闭联接。一旦收到消息,后端马上关闭联接并且退出。

在少数情况下(比如一个管理员命令数据库关闭),后端可能在没有任何前端请求的情况下断开连接。 在这种情况下,后端将在它断开连接之前尝试发送一个错误或者通知信息,给出断开的原因。

其它终止的情况发生在各种失效的场合,比如某一方的内核转储,失去通讯链路, 丢失了消息边界同步等。不管是前端还是后端看到了一个意外的连接关闭,那么它应该清理现场并且终止。 如果前端不想终止自己,那么它可以通过替换服务器的方法重启一个新的后端。 如果收到了一个无法识别的消息,那么我们也建议关闭连接,因为出现这种情况可能意味着是丢失了消息边界的同步。

不管是正常还是不正常的终止,任何打开的事务都会回滚,而不是提交。 不过,我们应该注意的是如果一个前端在一个非 SELECT 查询正在处理的时候断开, 那么后端很可能在注意到断开之前先完成查询的处理。 如果查询处于任何事务块之外(BEGIN ... COMMIT 序列),那么其结果很可能在得知断开之前被提交。 43.2.9. SSL 会话加密

如果制作 PostgreSQL 的时候打开了 SSL 支持,那么前后端通讯就可以用 SSL 加密。 这样就提供了一种在攻击者可能捕获会话通讯数据包的环境下保证通讯安全的方法。 有关使用 SSL 加密 PostgreSQL 会话的更多信息, 请参阅 Section 16.7。

要开始一次 SSL 加密联接,前端先是发送一个 SSLRequest 消息,而不是 StartupMessage。然后服务器以一个包含 S 或 N 的字节响应,分别表示它愿意还是不愿意进行 SSL。如果前端对响应不满意, 那么它可以关闭联接。要在 S 之后继续,那么先进行与服务器的 SSL 启动握手(没有在这里描述,是 SSL 规范的一部分)。 如果这些成功了,那么继续发送普通的 StartupMessage。这种情况下, StartupMessage 和所有随后的数据都将由 SSL 加密。要在 N 之后继续,则发送普通的 StartupMessage 然后不带加密进行处理。

前端应该也准备处理一个来自服务器的给 SSLRequest 的 ErrorMessage 响应。这种情况只有在服务器给 PostgreSQL 的 SSL 支持增加了附加的期望的情况下才会出现。 在这种情况下,联接必需关闭,但是前端可以选择打开一个新的联接然后不带 SSL 进行联接。

一个初始化的 SSLRequest 也可以用于打开来用于发送一条 CancelRequest 消息的联接中。

如果协议本身并未提供某种方法强制 SSL 加密,那么管理员可以把服务器配置为拒绝未加密的会话,这是认证检查的一个副产品。

43.3. 消息数据类型

本节描述消息里用到的基本数据类型。

Intn(i)

   一个网络字节序(译注:高位->高地址,底位->底地址)的 n 位整数。 如果声明了 i, 它就是将出现的确切值,否则这个数值就是一个变量。如 Int16,Int32(42)。

Intn[k]

   一个 k 个 n 位整数元素的数组, 每个都是以网络字节序存储的。数组长度 k 总是由消息前面的字段来判断的。比如,Int16[M]。

String(s)

   一个(C 风格的)空零结尾的字串。对字串没有特别的长度限制。 如果声明了 s,那么它是将出现的确切的数值, 否则这个数值就是一个变量。比如,String,String("user")。
       注意: 后端返回的字串的可能长度没有预定义的限制。所以前端必须使用良好的编码策略,使用某种可扩展的缓冲区以便能接受任何能放进内存里的东西。如果那样做不可行,则读取全长的字串然后抛弃不能放进你的定长缓冲区的尾部字符。

Byten(c)

   精确的 n 字节。 如果字段宽度 n 不是一个常量, 那么我们总是可以从消息中更早的字段中判断它。 如果声明了 c那么它是确切数值。 例如,Byte2, Byte1('\n')。

43.4. 消息格式

本节描述各种消息的详细格式.每种消息都标记为它是由一个前端 (F),一个后端 (B)或者两者(F & B)发送的。请注意,尽管每条消息在开头都包含一个字节计数,消息格式也定义为我们可以不用参考字节计数就可以找到消息的结尾。这样就增加了有效性检查。(CopyData 消息是一个例外,因为它形成一个数据流的一部分;任意独立的 CopyData 消息可能是无法自解释的。)

AuthenticationOk (B)

   Byte1('R')
       标识该消息是一条认证请求.
   Int32(8)
       以字节记的消息内容长度,包括这个长度本身。
   Int32(0)
       声明该认证是成功的。

AuthenticationKerberosV5 (B)

   Byte1('R')
       标识该消息是一条认证请求。
   Int32(8)
       以字节记的消息内容长度,包括长度自身。
   Int32(2)
       声明需要 Kerberos V5 认证。

AuthenticationCleartextPassword (B)

   Byte1('R')
       标识该消息是一条认证请求。
   Int32(8)
       以字节记的消息内容长度,包括长度自身。
   Int32(3)
       声明需要一个明文的口令。

AuthenticationCryptPassword (B)

   Byte1('R')
       标识该消息是一条认证请求。
   Int32(10)
       以字节记的消息内容的长度,包括长度本身。
   Int32(4)
       声明需要一个 crypt() 加密的口令。
   Byte2
       加密口令使用的盐粒(salt)。

AuthenticationMD5Password (B)

   Byte1('R')
       标识这条消息是一个认证请求。
   Int32(12)
       以字节记的消息内容的长度,包括长度本身。
   Int32(5)
       声明需要一个 MD5 加密的口令。
   Byte4
       加密口令的时候使用的盐粒。

AuthenticationSCMCredential (B)

   Byte1('R')
       标识这条消息是一个认证请求。
   Int32(8)
       以字节记的消息内容长度,包括长度本身。
   Int32(6)
       声明需要一个 SCM 信任消息。

BackendKeyData (B)

   Byte1('K')
       标识该消息是一个取消键字数据。 如果前端希望能够在稍后发出 CancelRequest 消息, 那么它必须保存这个值。
   Int32(12)
       以字节记的消息内容的长度,包括长度本身。
   Int32
       后端的进程号(PID)。
   Int32
       此后端的密钥(secret key )。

Bind (F)

   Byte1('B')
       标识该信息是一个绑定命令。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   String
       目标入口的名字(空字串则选取未命名的入口)。
   String
       源准备好语句的名字(空字串则选取未命名的准备好语句)。
   Int16
       后面跟着的参数格式代码的数目(在下面的 C 中说明)。 这个数值可以是零,表示没有参数,或者是参数都使用缺省格式(文本); 或者是一,这种情况下声明的格式代码应用于所有参数;或者它可以等于实际数目的参数。
   Int16[C]
       参数格式代码。目前每个都必须是零(文本)或者一(二进制)。
   Int16
       后面跟着的参数值的数目(可能为零)。这些必须和查询需要的参数个数匹配。
   然后,每个参数都会出现下面的字段对:
   Int32
       参数值的长度,以字节记(这个长度并不包含长度本身)。可以为零。 一个特殊的情况是,-1 表示一个 NULL 参数值。在 NULL 的情况下, 后面不会跟着数值字节。
   Byten
       参数值,格式是关联的格式代码标明的。n 是上面的长度。
   在最后一个参数之后,出现下面的字段:
   Int16
       后面跟着的结果字段格式代码数目(下面的 R 描述)。 这个数目可以是零表示没有结果字段,或者结果字段都使用缺省格式(文本); 或者是一,这种情况下声明格式代码应用于所有结果字段(如果有的话);或者它可以等于查询的结果字段的实际数目。
   Int16[R]
       结果字段格式代码。目前每个必须是零(文本)或者一(二进制)。

BindComplete (B)

   Byte1('2')
       标识消息为一个绑定结束标识符。
   Int32(4)
       以字节记的消息长度,包括长度本身。

CancelRequest (F)

   Int32(16)
       以字节计的消息长度。包括长度本身。
   Int32(80877102)
       取消请求代码。选这个值是为了在高16位包含 1234, 低16位包含 5678。(为避免混乱,这个代码必须与协议版本号不同.)
   Int32
       目标后端的进程号(PID)。
   Int32
       目标后端的密钥(secret key)。

Close (F)

   Byte1('C')
       标识这条消息是一个 Close 命令。
   Int32
       以字节记的消息内容长度,包括长度本身。
   Byte1
       'S' 关闭一个准备的语句;或者 'P' 关闭一个入口。
   String
       一个要关闭的准备好的语句或者入口的名字(一个空字串选择未命名的准备好语句或者入口)。

CloseComplete (B)

   Byte1('3')
       标识消息是一个 Close 完毕指示器。
   Int32(4)
       以字节记的消息内容的长度,包括长度本身。

CommandComplete (B)

   Byte1('C')
       标识此消息是一个命令结束响应。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   String
       命令标记。它通常是一个单字,标识那个命令完成。
       对于INSERT命令,标记是INSERT oidrows, 这里的rows是插入的行数。oid 在row为 1 并且目标表有 OID 的时候是插入行的对象 ID; 否则oid就是 0。
       对于DELETE 命令,标记是 DELETE rows, 这里的 rows 是删除的行数。
       对于 UPDATE 命令,标记是 UPDATE rows 这里的 rows 是更新的行数。
       对于 MOVE 命令,标记是 MOVE rows,这里的 rows 是游标未知改变的行数。
       对于 FETCH 命令,标记是 FETCH rows,这里的 rows 是从游标中检索出来的行数。

CopyData (F & B)

   Byte1('d')
       标识这条消息是一个 COPY 数据。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   Byten
       构成 COPY 数据流的一部分的数据。从后端发出的消息总是对应一个数据行,但是前端发出的消息可以任意分割数据流。

CopyDone (F & B)

   Byte1('c')
       标识这条信息是一个 COPY 结束指示器。
   Int32(4)
       以字节记的消息内容长度,包括长度本身。

CopyFail (F)

   Byte1('f')
       标识这条消息是一个 COPY 失败指示器。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   String
       一个报告失败原因的错误信息。

CopyInResponse (B)

   Byte1('G')
       标识这条消息是一条 Start Copy In (开始拷贝进入)响应消息。 前端现在必须发送一条拷贝入数据。(如果还没准备好做这些事情, 那么发送一条 CopyFail 消息)。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   Int8
       0 表示全部的 COPY 格式都是文本的(数据行由换行符分隔,字段由分隔字符分隔等等)。 1 表示全部 COPY 格式都是二进制的(类似 DataRow 格式)。 参阅 COPY 获取更多信息。
   Int16
       数据中要拷贝的字段数(由下面的 N 解释)。
   Int16[N]
       每个字段将要用的格式代码,目前每个都必须是零(文本)或者一(二进制)。 如果全部拷贝格式都是文本的,那么所有的都必须是零。

CopyOutResponse (B)

   Byte1('H')
       标识这条消息是一条 Start Copy Out (开始拷贝进出)响应消息。 这条消息后面将跟着一条拷贝出数据消息。
   Int32
       以字节记的消息内容的长度,包括它自己。
   Int8
       0 表示全部拷贝格式都是文本(数据行由换行符分隔, 字段由分隔字符分隔等等)。1 表示所有拷贝格式都是二进制的(类似于 DataRow 格式)。参阅 COPY 获取更多信息。
   Int16
       要拷贝的数据的字段的数目(在下面的 N 说明)。
   Int16[N]
       每个字段要试用的格式代码。目前每个都必须是零(文本)或者一(二进制)。 如果全部的拷贝格式都是文本,那么所有的都必须是零。

DataRow (B)

   Byte1('D')
       标识这个消息是一个数据行。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   Int16
       后面跟着的字段值的个数(可能是零)。
   然后,每个字段都会出现下面的数据域对:
   Int32
       字段值的长度,以字节记(这个长度不包括它自己)。 可以为零。一个特殊的情况是,-1 表示一个 NULL 的字段值。 在 NULL 的情况下就没有跟着数据字段。
   Byten
       一个字段的数值,以相关的格式代码表示的格式展现。 n 是上面的长度。

Describe (F)

   Byte1('D')
       标识消息是一个 Describe (描述)命令。
   Int32
       以字节记的消息内容的长度,包括字节本身。
   Byte1
       'S' 描述一个准备好的语句;或者 'P' 描述一个入口。
   String
       要描述的准备好的语句或者入口的名字(或者一个空字串,就会选取未命名的准备好语句或者入口)。

EmptyQueryResponse (B)

   Byte1('I')
       标识这条消息是对一个空查询字串的响应。 (这个消息替换了 CommandComplete。) 
   Int32(4)
       以字节记的消息内容长度,包括它自己。

ErrorResponse (B)

   Byte1('E')
       标识消息是一条错误。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   消息体由一个或多个标识出来的字段组成,后面跟着一个字节零作为终止符。 字段可以以任何顺序出现。对于每个字段都有下面的东西:
   Byte1
       一个标识字段类型的代码;如果为零,这就是消息终止符并且不会跟着有字串。 目前定义的字段类型在 Section 43.5 列出。 因为将来可能增加更多的字段类型,所以前端应该不声不响地忽略不认识类型的字段。
   String
       字段值。

Execute (F)

   Byte1('E')
       标识消息识一个 Execute 命令。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   String
       要执行的入口的名字(空字串选定未命名的入口)。
   Int32
       要返回的最大行数,如果入口包含返回行的查询(否则忽略)。 零标识"没有限制"。

Flush (F)

   Byte1('H')
       标识消息识一条 Flush 命令。
   Int32(4)
       以字节记的消息内容的长度,包括长度本身。

FunctionCall (F)

   Byte1('F')
       标识消息是一个函数调用.
   Int32
       以字节记的消息内容的长度,包括长度本身。
   Int32
       声明待调用的函数的对象标识(OID)。
   Int16
       后面跟着的参数格式代码的数目(用下面的C表示)。 它可以是零,表示没有参数,或者是所有参数都试用缺省格式(文本); 或者是一,这种情况下声明的格式代码应用于所有参数;或者它可以等于参数的实际个数。
   Int16[C]
       参数格式代码。目前每个必须是零(文本)或者一(二进制)。
   Int16
       声明提供给函数的参数个数。
   然后,每个参数都出现下面字段对:
   Int32
       以字节记的参数值的长度(不包括长度自己)。可以为零。 一个特殊的例子是,-1 表示一个 NULL 参数值。如果是 NULL,则没有参数字节跟在后面。
   Byten
       参数的值,格式是用相关的格式代码表示的。 n是上面的长度。
   在最后一个参数之后,出现下面的字段:
   Int16
       函数结果的格式代码。目前必须是零(文本)或者一(二进制)。

FunctionCallResponse (B)

   Byte1('V')
       标识这条消息是一个函数调用结果。
   Int32
       以字节记的消息内容长度,包括长度本身。
   Int32
       以字节记的函数结果值的长度(不包括长度本身)。可以为零。一个特殊的情况是 -1 表示 NULL 函数结果。如果是 NULL 则后面没有数值字节跟随。
   Byten
       函数结果的值,格式是相关联的格式代码标识的。 n 是上面的长度。

NoData (B)

   Byte1('n')
       标识这条消息是一个无数据指示器。
   Int32(4)
       以字节记的消息内容长度,包括长度本身。

NoticeResponse (B)

   Byte1('N')
       标识这条消息是一个通知。
   Int32
       以字节记的消息内容长度,包括长度本身。
   消息体由一个或多个标识字段组成,后面跟着字节零作为中止符。 字段可以以任何顺序出现。对于每个字段,都有下面的东西:
   Byte1
       一个标识字段类型的代码;如果为零,那么它就是消息终止符,并且后面不会跟着字串。 目前定义的字段类型在 Section 43.5里列出。 因为将来可能会增加更多字段类型,所以前端应该将不识别的字段安静地忽略掉。
   String
       字段值。

NotificationResponse (B)

   Byte1('A')
       标识这条消息是一个通知响应。
   Int32
       以字节记地消息内容地长度,包括长度本身。
   Int32
       通知后端进程地进程 ID。 
   String
       触发通知的条件的名字。 
   String
       从通知进程传递过来的额外的信息。(目前,这个特性还未实现,因此这个字段总是一个空字串。)

ParameterDescription (B)

   Byte1('t')
       标识消息是一个参数描述。
   Int32
       以字节记的消息内容长度,包括长度本身。
   Int16
       语句所使用的参数的个数(可以为零)。
   然后,对每个参数,有下面的东西。
   Int32
       声明参数数据类型的对象 ID。

ParameterStatus (B)

   Byte1('S')
       标识这条消息是一个运行时参数状态报告。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   String
       被报告的运行时参数的名字。
   String
       参数的当前值。

Parse (F)

   Byte1('P')
       标识消息是一条 Parse 命令。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   String
       目的准备好语句的名字(空字串表示选取了未命名的准备好语句)。
   String
       要分析的查询字串。
   Int16
       声明的参数数据类型的数目(可以为零)。请注意这个参数并不意味着可能在查询字串里出现的参数个数的意思, 只是前端希望预先声明的类型的数目。
   然后,对每个参数,有下面的东西:
   Int32
       声明参数数据类型的对象 ID。在这里放一个零等效于不声明该类型。

ParseComplete (B)

   Byte1('1')
       标识这条消息是一个 Parse 完成指示器。
   Int32(4)
       以字节记的消息内容长度,包括长度自身。

PasswordMessage (F)

   Byte1('p')
       标识这条消息是一个口令响应。
   Int32
       以字节记的消息内容的长度,包括长度本身。
   String
       口令(如果要求了,就是加密后的)。

PortalSuspended (B)

   Byte1('s')
       标识这条消息是一个入口挂起指示器。请注意这个消息只出现在达到一条 Execute 消息的行计数限制的时候。
   Int32(4)
       以字节记的消息内容的长度,包括长度自身。

Query (F)

   Byte1('Q')
       标识消息是一个简单查询。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   String
       查询字串自身。

ReadyForQuery (B)

   Byte1('Z')
       标识消息类型。在后端为新的查询循环准备好的时候, 总会发送 ReadyForQuery。
   Int32(5)
       以字节记的消息内容的长度,包括长度本身。
   Byte1
       当前后端事务状态指示器。可能的值是空闲状况下的 'I'(不在事务块里);在事务块里是 'T'; 或者在一个失败的事务块里是 'E'(在事务块结束之前,任何查询都将被拒绝)。

RowDescription (B)

   Byte1('T')
       标识消息是一个行描述。
   Int32
       以字节记的消息内容的长度,包括长度自身。
   Int16
       声明在一个行里面的字段数目(可以为零)。
   然后对于每个字段,有下面的东西:
   String
       字段名字。
   Int32
       如果字段可以标识为一个特定表的字段,那么就是表的对象 ID; 否则就是零。
   Int16
       如果该字段可以标识为一个特定表的字段,那么就是该表字段的属性号;否则就是零。
   Int32
       字段数据类型的对象 ID。
   Int16
       数据类型尺寸(参阅pg_type.typlen)。 请注意负数表示变宽类型。
   Int32
       类型修饰词(参阅pg_attribut.atttypmod)。 修饰词的含义是类型相关的。
   Int16
       用于该字段的格式码。目前会是零(文本)或者一(二进制)。 从语句变种 Describe 返回的 RowDescription 里,格式码还是未知的,因此总是零。

SSLRequest (F)

   Int32(8)
       以字节记的消息内容的长度,包括长度本身。
   Int32(80877103)
       SSL 请求码。选取的数值在高16位里包含 1234,在低16位里包含 5679。 (为了避免混淆,这个编码必须和任何协议版本号不同。)

StartupMessage (F)

   Int32
       以字节记的消息内容长度,包括长度自身。
   Int32(196608)
       协议版本号。高 16 位是主版本号(对这里描述的协议而言是 3)。 低 16 位是次版本号(对于这里描述的协议而言是 0)。
   协议版本号后面跟着一个或多个参数名和值字串的配对。要求在最后一个名字/数值对后面有个字节零。 参数可以以任意顺序出现。user是必须的,其它都是可选的。每个参数都是这样声明的:
   String
       参数名。目前可以识别的名字是:
       user
           用于连接的数据库用户名。必须;无缺省。
       database
           要连接的数据库。缺省是用户名。
       options
           给后端的命令行参数。(这个特性已经废弃,更好的方法是设置单独的运行时参数。)
       除了上面的外,在后端启动的时候可以设置的任何运行时参数都可以列出来。 这样的设置将在后端启动的时候附加(在分析了命令行参数之后---如果有的话)。这些值将成为会话缺省。
   String
       参数值。

Sync (F)

   Byte1('S')
       表示该消息为一条 Sync 命令。
   Int32(4)
       以字节记的消息内容的长度,包括长度自身。

Terminate (F)

   Byte1('X')
       标识消息是一个终止消息。
   Int32(4)
       以字节记的消息内容的长度,包括长度自身。 

43.5. 错误和通知消息字段

本节描述那些可能出现在 ErrorResponse 和 NoticeResponse 消息里的字段。每个字段类型有一个单字节标识记号。请注意,任意给定的字段类型在每条信息里都应该最多出现一次。

S

   严重性:该字段的内容是ERROR,FATAL, 或者 PANIC(在一个错误消息里),或者 WARNING, NOTICE,DEBUG,INFO 或 LOG (在一条通知消息里),或者是这些的某种本地化翻译的字串。总是会出现。

C

   代码:错误的 SQLSTATE 代码(参阅 Appendix A)。 不能本地化。总是出现。

M

   消息:人类可读的错误信息的主体。这些信息应该准确并且简洁(通常是一行)。总是出现。

D

   细节:一个可选的从属错误信息,承载有关问题的更多错误消息。 可以是多行。

H

   提示:一个可选的有关如何处理问题的建议。它和细节不同的地方是它提出了建议(可能并不合适)而不仅仅是事实。可以是多行。

P

   位置:这个字段值是一个十进制 ASCII 整数,表示一个错误游标的位置, 它是一个指向原始查询字串的索引。第一个字符的索引是 1,位置是以字符计算而非字节计算的。

p

   内部位置:这个域和 P 域定义相同,但是它用于游标的位置指向一个内部生成的命令, 而不是用于客户端提交的命令。这个字段出现的时候,总是会出现 q 字段。

q

   内部查询:失效的内部生成的命令的文本。 比如,它可能是一个 PL/pgSQL 函数发出的 SQL 查询。

W

   哪里:一个指示错误发生的环境的指示器。目前, 这个参数包含一个活跃的过程语言函数的调用堆栈的追溯和内部生成的查询。 这个追溯每条记录一行,最新的在最上面。

F

   文件:所报告错误在源代码中的位置。

L

   行:报告的错误所在的源代码的位置的行号。

R

   过程:报告错误的过程在源代码中的名字。

客户端负责对显示信息进行格式化输出以符合需要;特别是它应该根据需要断开长的行。在错误信息字段里出现的换行符应该当作一个分段的符号,而不是换行。


43.6. 自协议 2.0 以来的变化的概述

本节提供一个快速的变化检查列表,以便于那些视图将现有的客户端库更新到 3.0 协议的开发人员。

初始化的启动包用了一个灵活的字串列表格式取代了固定的格式。请注意,运行时参数的会话缺省值现在可以直接在启动包中声明。(实际上,你可以在使用 options 字段之前干这件事情,但是因为 options 的宽度限制以及缺乏引起数值中的空白的方法,这并不是很安全的技巧。)

现在所有的消息在消息类型字节后面都有一个长度计数(除了启动包之外,它没有类型字节)。同时还要注意现在 PasswordMessage 有一个类型字节。

ErrorResponse 和 NoticeResponse('E' 和 'N')消息现在包含多个字段,从这些字段里客户端代码可以组合出自己所希望的详细程度的错误信息。请注意独立的字段通常不是用换行符终止的,虽然在老协议里发送的单个字串总是会用换行符终止。

ReadyForQuery ('Z')消息包括一个事务状态指示符。

BinaryRow 和 DataRow 消息类型之间的区别不再存在了;单个 DataRow 消息类型用于返回所有格式的数据。请注意 DataRow 的布局已经改变成比较容易分析。同样,二进制数值的表现形式已经改变了:它不再是直接和服务器的内部表现形式绑定。

有了一种新的"扩展查询"的子协议,它增加了前端消息类型 parse,Bind, Execute,Describe,Close,Flush,和 Sync,以及后端消息类型 ParseComplete, BindComplete,PortalSuspended,ParameterDescription, NoData,和 CloseComplete。现有的客户端不用关心这个子协议,但是利用这个子协议将令我们可能改进性能或者功能。

COPY 数据现在封装到了 CopyData 和 CopyDone 消息里。现在有种很好的方法从正在进行的 COPY 动作中的错误恢复。最后一行的特殊的 "\." 不再必须了,并且在 COPY OUT 的过程中不再发送。(在 COPY IN 的时候它仍然被认为是一个终止符,但是它的使用已经废弃了并且最终将被删除。)现在支持二进制 COPY。CopyInResponse 和 CopyOutResponse 消息包括只是字段数目和每个字段格式的信息域。

FunctionCall 和 FunctionCallResponse 消息的布局变化了。 FunctionCall 现在支持给函数传递 NULL 参数。它同样可以处理以文本或者二进制格式传递参数和检索结果。我们不用在认为 FunctionCall 有潜在的安全性漏洞,因为它并不提供对内部服务器数据表现形式的直接访问。

后端在启动的时候为它认为客户端库感兴趣的所有参数发送发送 ParameterStatus('S')消息。随后,如果这些参数的任何活跃值发生变化,那么发送一条 ParameterStatus 消息。

RowDescription 为所描述的行的每个字段 ('T')消息运载新表的 OID 和字段号数据域。它同样还为每个字段显示了格式代码。

后端不再生成 CursorResponse ('P')消息。

NotificationResponse ('A')消息有一个额外的字串域,它目前是空的,但是以后可能可以运自阿来自 NOTIFY 时间发送者运载的额外数据。

EmptyQueryResponse ('I')以前包含一个空字串参数;这个已经删除了。