内容简介:本文侧重 PostgreSQL 字符编码的代码逻辑,跟踪字符编码的几个关键变量的起始变化,解释一些字符编码的相关函数。PostgreSQL 字符编码的背景知识请参考官方网站,或者看一下我之前总结的在这里简要提一下,数据库创建时需要指定三个与字符编码相关的变量,分别是字符编码、LC_CTYPE 和 LC_COLLATE,而字符编码需要和 LC_CTYPE 字符区域相兼容;数据库和客户端的字符编码可以不一致,PostgreSQL 代码会自动完成相关转换。
本文侧重 PostgreSQL 字符编码的代码逻辑,跟踪字符编码的几个关键变量的起始变化,解释一些字符编码的相关函数。
PostgreSQL 字符编码背景知识
PostgreSQL 字符编码的背景知识请参考官方网站,或者看一下我之前总结的 PostgreSQL 字符编码总结 。
在这里简要提一下,数据库创建时需要指定三个与字符编码相关的变量,分别是字符编码、LC_CTYPE 和 LC_COLLATE,而字符编码需要和 LC_CTYPE 字符区域相兼容;数据库和客户端的字符编码可以不一致,PostgreSQL 代码会自动完成相关转换。
查看 PostgreSQL 数据库编码方式
PostgreSQL 会将数据库使用的编码方式存于 pg_database 系统表。使用 psql 可以执行 “\l”查看,或者使用 SQL 命令:
select datname,pg_encoding_to_char(encoding) as encoding from pg_database;
PostgreSQL 与客户端链接建立过程
链接是基于 TCP 的,三次握手成功后,客户端与 PostgreSQL 协商是否使用 SSL 后,客户端需要主动发送一个 startup 包,声明协议版本号、用户名、数据库名等信息,其中可以包括一些 GUC 参数,比如客户端使用的编码方式、时区等。
PostgreSQL 字符编码的始终
当新的客户端连接到某个数据库时,postmaster 新建子进程 postgres 处理与该客户端的会话,子进程从 startup 包内获取到了此次会话的协议版本号、用户名、数据库名,以及一些类似于编码方式、时区等 GUC 相关的环境变量设置,postgres 子进程依据这些完成初始化工作。对于字符编码而言,子进程就需要获取到目标数据库的编码方式,客户端使用的编码方式,检查这两种编码方式是否相兼容,并找到这两种编码转换的方法。
紧接着,客户端使用它自己的字符编码读写目标数据库。在子进程 postgres 看来,写操作就是接收到客户端编码的内容后,将其转换为目标数据库的编码并写入数据库;而读操作就是从数据库文件中读取数据库编码的内容,将其转换为客户端编码并发送给客户端。在子进程 postgres 处理读写过程中,可能需要发送一些纯英文字母来标识读写的某些状态,因为纯英文字母是属于标准 ASCII 的,所以这些字母不需要做编码转换。
可能后续该客户端可使用下述一些方式改变其使用的字符编码方式。而 postgres 子进程收到这些 SQL 命令后,就会尝试改变之前存的客户端的编码方式,检查与目标数据库编码的兼容性,查找字符转换函数。
SET CLIENT_ENCODING TO 'value'; SET NAMES 'value'; RESET client_encoding;
PostgreSQL 字符编码的几个关键变量
与字符编码相关的代码主要集中在 mbutils.c 文件中,这几个关键字符编码变量始终贯穿其中:
static List *ConvProcList = NIL; /* List of ConvProcInfo */ /* * These variables point to the currently active conversion functions, * or are NULL when no conversion is needed. */ static FmgrInfo *ToServerConvProc = NULL; static FmgrInfo *ToClientConvProc = NULL; /* * These variables track the currently-selected encodings. */ static const pg_enc2name *ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; static const pg_enc2name *DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; static const pg_enc2name *MessageEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; /* * During backend startup we can't set client encoding because we (a) * can't look up the conversion functions, and (b) may not know the database * encoding yet either. So SetClientEncoding() just accepts anything and * remembers it for InitializeClientEncoding() to apply later. */ static bool backend_startup_complete = false; static int pending_client_encoding = PG_SQL_ASCII;
ConvProcList
这是编码转换函数列表。
ToServerConvProc
当前使用的将编码转换为数据库编码的函数。
ToClientConvProc
当前使用的将编码转换为客户端编码的函数。
ClientEncoding
当前客户端使用的编码方式。
DatabaseEncoding
目标数据库使用的编码方式。
MessageEncoding
错误提示信息使用的编码方式。
backend_startup_complete
子进程 postgres 是否初始化完成的标志。
pending_client_encoding
即将成为客户端编码的编码方式。
client_encoding
GUC 参数,用于配置,可使用 GetConfigOptionByName(“client_encoding”, NULL, true) 查看其值。
PostgreSQL 字符编码函数
pg_get_client_encoding()
获取当前客户端编码的枚举值。
pg_get_client_encoding_name()
获取当前客户端编码的名字。
GetDatabaseEncoding()
获取目标数据库编码的枚举值。
GetDatabaseEncodingName()
获取目标数据库编码的名字。
SetDatabaseEncoding()
设置目标数据库编码方式。
SetClientEncoding()
设置客户端编码方式,当子进程 postgres 未完成初始化时,只能设置到 pending_client_encoding 变量中。
pg_client_to_server()
将客户端编码的内容转换成目标数据库的编码格式。
pg_server_to_client()
将数据库编码的内容转换为客户端编码格式。
pq_sendtext() pq_sendstring()
此类函数用于子进程 postgres 将内容发送给客户端,内部完成了编码转换。
PostgreSQL 字符编码的初始化
在子进程 postgres 初始化时完成了字符编码的初始化,主要工作在 InitPostgres() 函数中完成,主要涉及到了三个函数的调用。
调用 CheckMyDatabase() 函数
从 pg_database 系统表获取字符编码,设置到 DatabaseEncoding 变量中,同时设置到 GUC 中 “server_encoding” 和 “client_encoding” 的值,所以这两个 GUC 值默认是目标数据库的编码方式,比如 EUC_KR;需要注意的是,这时子进程没有完成初始化,即 backend_startup_complete 为 false,pending_client_encoding 被设值,而 ClientEncoding 还是默认值 SQL_ASCII。另外,MessageEncoding 也是在这里完成了初始设置。
调用 process_startup_options() 函数
处理 startup 包内的 GUC 参数,此时 backend_startup_complete 为 false,会调用 “client_encoding” 对应的回调函数 SetClientEncoding() 设置 pending_client_encoding 值。
调用 InitializeClientEncoding() 函数
InitializeClientEncoding() 函数最终完成客户端编码的设置。该函数一开始就设置 backend_startup_complete 为 true,将 pending_client_encoding 的值设置到 ClientEncoding 中,并在 ConvProcList 列表中依据目标数据库和客户端编码查找到编码转换函数,设置 ToServerConvProc、ToClientConvProc 两个变量。
PostgreSQL 字符编码的改变
若客户端改变了编码方式,就会走到正常的处理流程 standard_ProcessUtility() -> ExecSetVariableStmt(),最终会走到 GUC 参数设置,走到回调函数 SetClientEncoding(),从而完成了客户端编码设置。
PostgreSQL 字符编码的自动转换
客户端编码转到数据库编码
比如,客户端使用扩展查询方式,执行普通的插入操作,发给 PostgreSQL 的数据采用了 UTF8 编码,绑定的变量使用了 text 模式,在子进程 postgres 中 exec_bind_message() 时,就需要 pg_client_to_server() 做编码转换。
数据库编码转换到客户端编码
比如,PostgreSQL 返回 text 模式的数据时,就会做编码转换。
libpq 库实现相关
libpq 中有个关键的数据结构 pg_conn,内部有个 client_encoding 变量用以标识该链接使用的字符编码,可使用 PQsetClientEncoding() 设置链接的字符编码。
在 PostgreSQL 数据库与客户端协议 2 版本中,在设置完字符编码后,需要 pqSaveParameterStatus() 保存设置的字符编码;而在协议 3 版本中,依据协议规定,设置字符编码后,PostgreSQL 会自动报告一下设置后的字符编码(回复 S 消息),因此在 pqParseInput3()->case ‘S’ 时获取字符编码并保存到 pg_conn->client_encoding 即可。
Advertisements
以上所述就是小编给大家介绍的《PostgreSQL 字符编码相关代码解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。