赞
踩
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-10.1 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书以及一些相关资料。
PostgreSQL 处理客户端和服务器端字符集匹配问题的机制是复杂且灵活的,能够确保数据在不同编码之间传输时保持其原始的意义和结构,从而避免了乱码问题。这一机制主要涉及字符集的识别、转换和验证过程,以确保从客户端发送到服务器的数据能够被正确解释和存储,即使客户端和服务器使用的是不同的字符编码(例如,客户端使用 GBK,而服务器使用 UTF-8)。以下是这个过程的详细描述:
PostgreSQL 通过内建的编码识别和转换机制,能够灵活地处理客户端和服务器之间不同字符编码的匹配问题。这一机制确保了数据在传输过程中的准确性和有效性,让客户端和服务器即使在使用不同的编码时也能无缝交互,极大地减少了乱码问题的发生。
以下代码定义了两个函数 pg_client_to_server 和 pg_any_to_server,用于处理字符串的编码转换,确保数据在客户端和服务器之间传输时保持正确的编码格式。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c
)
/*
* 将客户端编码的字符串转换为服务器编码。
*
* 请参阅本文件顶部有关字符串转换函数的注释。
*/
char *
pg_client_to_server(const char *s, int len)
{
// 调用pg_any_to_server函数,将字符串从客户端编码转换为服务器编码
return pg_any_to_server(s, len, ClientEncoding->encoding);
}
/*
* 将任意编码的字符串转换为服务器编码。
*
* 请参阅本文件顶部有关字符串转换函数的注释。
*
* 与其他字符串转换函数不同,即使encoding == DatabaseEncoding->encoding,
* 也会应用验证。这是因为它用于处理来自数据库外部的数据,
* 我们永远不想仅仅假设数据是有效的。
*/
char *
pg_any_to_server(const char *s, int len, int encoding)
{
if (len <= 0)
return (char *) s; /* 如果字符串长度小于等于0,直接返回,空字符串总是有效的 */
if (encoding == DatabaseEncoding->encoding ||
encoding == PG_SQL_ASCII)
{
/*
* 如果不需要转换,但我们仍需验证数据。
*/
(void) pg_verify_mbstr(DatabaseEncoding->encoding, s, len, false);
return (char *) s;
}
if (DatabaseEncoding->encoding == PG_SQL_ASCII)
{
/*
* 如果不可能转换,但我们仍需验证数据,
* 因为客户端代码可能已经使用选定的client_encoding进行了字符串转义。
* 如果客户端编码对ASCII是安全的,则我们只需按该编码直接验证。
* 对于一个ASCII不安全的编码,我们有一个问题:我们不敢将这样的数据传递给解析器,
* 但我们没有办法转换它。我们通过拒绝包含任何非ASCII字符的数据来妥协。
*/
if (PG_VALID_BE_ENCODING(encoding))
(void) pg_verify_mbstr(encoding, s, len, false);
else
{
int i;
for (i = 0; i < len; i++)
{
if (s[i] == '\0' || IS_HIGHBIT_SET(s[i]))
ereport(ERROR,
(errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE),
errmsg("invalid byte value for encoding \"%s\": 0x%02x",
pg_enc2name_tbl[PG_SQL_ASCII].name,
(unsigned char) s[i])));
}
}
return (char *) s;
}
/* 如果我们能使用缓存的转换函数,这是一个快速路径 */
if (encoding == ClientEncoding->encoding)
return perform_default_encoding_conversion(s, len, true);
/* 通用情况...在事务外部不会工作 */
return (char *) pg_do_encoding_conversion((unsigned char *) s,
len,
encoding,
DatabaseEncoding->encoding);
}
pg_do_encoding_conversion 函数是一个用于执行字符串编码转换的通用函数。它处理从源编码到目标编码的转换,同时确保转换过程中字符串的有效性和正确性。这个函数首先检查是否需要进行转换,如果源编码和目标编码相同,或者目标编码是 SQL_ASCII,它将直接返回源字符串。对于 SQL_ASCII 源编码的字符串,会进行验证而不是转换。如果转换是必需的,该函数会查找适当的转换函数,进行转换,并返回转换后的字符串。这个过程发生在事务状态中,确保了数据的一致性和安全性。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c
)
/*
* 将源字符串转换为另一种编码(通用情况)。
*
* 请参阅本文件顶部有关字符串转换函数的注释。
*/
unsigned char *
pg_do_encoding_conversion(unsigned char *src, int len,
int src_encoding, int dest_encoding)
{
unsigned char *result;
Oid proc;
if (len <= 0)
return src; /* 如果字符串为空或长度小于等于0,直接返回源字符串,空字符串总是有效的 */
if (src_encoding == dest_encoding)
return src; /* 如果源编码和目标编码相同,不需要转换,直接返回源字符串 */
if (dest_encoding == PG_SQL_ASCII)
return src; /* 如果目标编码是SQL_ASCII,任何字符串都是有效的,直接返回源字符串 */
if (src_encoding == PG_SQL_ASCII)
{
/* 如果源编码是SQL_ASCII,不能转换,但我们必须验证结果 */
(void) pg_verify_mbstr(dest_encoding, (const char *) src, len, false);
return src;
}
if (!IsTransactionState()) /* 这种情况不应该发生 */
elog(ERROR, "cannot perform encoding conversion outside a transaction");
proc = FindDefaultConversionProc(src_encoding, dest_encoding);
if (!OidIsValid(proc))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("default conversion function for encoding \"%s\" to \"%s\" does not exist",
pg_encoding_to_char(src_encoding),
pg_encoding_to_char(dest_encoding))));
/*
* 为转换结果分配空间,注意整数溢出
*/
if ((Size) len >= (MaxAllocSize / (Size) MAX_CONVERSION_GROWTH))
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("out of memory"),
errdetail("String of %d bytes is too long for encoding conversion.",
len)));
result = palloc(len * MAX_CONVERSION_GROWTH + 1);
OidFunctionCall5(proc,
Int32GetDatum(src_encoding),
Int32GetDatum(dest_encoding),
CStringGetDatum(src),
CStringGetDatum(result),
Int32GetDatum(len));
return result;
}
其中需要注意以下几点:
- 事务状态检查: 转换必须在事务状态下进行,以确保数据的一致性。
- 转换函数查找: 通过 FindDefaultConversionProc 查找适合的编码转换函数。
- 内存管理: 为了防止整数溢出,转换结果分配的空间基于源字符串长度乘以一个最大增长率(MAX_CONVERSION_GROWTH)进行计算,以避免分配过多内存。
- 转换执行: 使用找到的转换函数执行实际的编码转换。
FindDefaultConversionProc 函数的主要作用和功能是在给定的数据库系统中,根据输入的源编码和目标编码来查找一个默认的编码转换过程。它通过遍历数据库的搜索路径(即一系列命名空间),在每个命名空间中尝试查找匹配的编码转换过程。如果在某个命名空间中找到了一个有效的转换过程,函数就会返回这个过程的标识符(Oid)。如果在所有的搜索路径中都没有找到有效的转换过程,则返回一个无效的标识符(InvalidOid)。这个机制允许数据库动态地处理字符集转换,提高了数据库系统处理不同编码数据的灵活性和兼容性。函数源码如下所示:(路径:src\backend\catalog\namespace.c
)
/*
* FindDefaultConversionProc - 查找默认的编码转换过程
*/
Oid
FindDefaultConversionProc(int32 for_encoding, int32 to_encoding)
{
Oid proc; // 定义一个Oid变量proc,用于存储找到的转换过程的标识符
ListCell *l; // 定义一个ListCell指针l,用于遍历搜索路径
recomputeNamespacePath(); // 重新计算命名空间路径,确保搜索路径是最新的
foreach(l, activeSearchPath) // 遍历当前激活的搜索路径
{
Oid namespaceId = lfirst_oid(l); // 获取当前命名空间的ID
if (namespaceId == myTempNamespace)
continue; /* 如果是临时命名空间,则跳过,不在临时命名空间中查找 */
proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding); // 尝试在当前命名空间中找到默认的转换过程
if (OidIsValid(proc)) // 如果找到有效的转换过程
return proc; // 返回找到的转换过程的标识符
}
/* 如果在搜索路径中没有找到 */
return InvalidOid; // 返回一个无效的Oid
}
FindDefaultConversion 函数的作用是在指定的命名空间中,根据给定的源编码和目标编码,查找默认的转换过程,并返回该转换过程的 OID。首先通过系统缓存搜索到符合条件的转换过程列表,然后遍历列表中的每个元素,检查是否为默认转换过程,如果是则获取其 OID 并返回,否则返回无效的 OID。函数源码如下所示:(路径:D:\pg相关src\backend\catalog\pg_conversion.c
)
/*
* FindDefaultConversion
*
* 在给定的命名空间中通过给定的源编码和目标编码找到“默认”转换过程。
*
* 如果找到,则返回该过程的OID,否则返回InvalidOid。请注意,您得到的是过程的OID,而不是转换的OID!
*/
Oid
FindDefaultConversion(Oid name_space, int32 for_encoding, int32 to_encoding)
{
CatCList *catlist; // 定义一个指向系统目录缓存列表的指针
HeapTuple tuple; // 定义一个堆元组变量
Form_pg_conversion body; // 定义一个指向pg_conversion系统表行数据的指针
Oid proc = InvalidOid; // 初始化过程OID为InvalidOid
int i; // 定义循环变量
// 通过指定的命名空间、源编码和目标编码搜索系统缓存中的转换过程列表
catlist = SearchSysCacheList3(CONDEFAULT,
ObjectIdGetDatum(name_space),
Int32GetDatum(for_encoding),
Int32GetDatum(to_encoding));
// 遍历转换过程列表中的每个元素
for (i = 0; i < catlist->n_members; i++)
{
tuple = &catlist->members[i]->tuple; // 获取当前元素的元组
body = (Form_pg_conversion) GETSTRUCT(tuple); // 获取当前元素对应的转换过程数据
// 如果当前转换过程为默认转换过程
if (body->condefault)
{
proc = body->conproc; // 获取该转换过程的OID
break; // 跳出循环
}
}
ReleaseSysCacheList(catlist); // 释放系统缓存列表
return proc; // 返回找到的默认转换过程的OID
}
我们用一个简单的案例来更好地描述以上场景:
conname | name_space | for_encoding | to_encoding | condefault | conproc |
---|---|---|---|---|---|
conv1 | 2200 | 6 | 8 | false | 1001 |
conv2 | 2200 | 8 | 6 | true | 1002 |
conv3 | 2200 | 6 | 9 | true | 1003 |
Oid defaultConversionOid = FindDefaultConversion(2200, 8, 6);
在这个例子中,我们希望找到命名空间为 2200,源编码为 8,目标编码为 6 的默认转换过程的 OID。
- 从系统缓存中检索所有符合条件的转换过程。
- 遍历转换过程列表,查找符合条件的默认转换过程。
- 如果找到了符合条件的默认转换过程,则返回该转换过程的 OID;如果没有找到,则返回 InvalidOid。
在这个例子中,由于我们指定的源编码为 8,目标编码为 6,并且要求的是默认转换过程,所以函数会返回转换过程 “conv2” 的 OID,即 1002。
以下代码实现了在 PostgreSQL 数据库中将服务器编码转换为客户端编码或任意其他编码的功能。pg_server_to_client 函数将调用 pg_server_to_any 函数,并将客户端编码作为目标编码传递。pg_server_to_any 函数根据提供的目标编码执行不同的操作。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c
)
/*
* 将服务器编码转换为客户端编码。
*
* 请参阅本文件顶部关于字符串转换函数的注释。
*/
char *
pg_server_to_client(const char *s, int len)
{
// 调用 pg_server_to_any 函数,将服务器编码转换为客户端编码
return pg_server_to_any(s, len, ClientEncoding->encoding);
}
/*
* 将服务器编码转换为任意编码。
*
* 请参阅本文件顶部关于字符串转换函数的注释。
*/
char *
pg_server_to_any(const char *s, int len, int encoding)
{
// 如果字符串长度小于等于 0,则返回原始字符串,空字符串始终有效
if (len <= 0)
return (char *) s; // 返回原始字符串
// 如果目标编码与数据库编码相同,或者目标编码为 PG_SQL_ASCII,则返回原始字符串,假定数据有效
if (encoding == DatabaseEncoding->encoding ||
encoding == PG_SQL_ASCII)
return (char *) s; // 返回原始字符串
// 如果数据库编码为 PG_SQL_ASCII,则无法进行转换,但是需要验证结果是否有效
if (DatabaseEncoding->encoding == PG_SQL_ASCII)
{
// 调用 pg_verify_mbstr 函数验证多字节字符串是否有效,并返回原始字符串
(void) pg_verify_mbstr(encoding, s, len, false);
return (char *) s; // 返回原始字符串
}
// 如果目标编码与客户端编码相同,可以使用缓存的转换函数进行快速转换
if (encoding == ClientEncoding->encoding)
// 调用 perform_default_encoding_conversion 函数,使用默认转换函数进行快速转换并返回结果
return perform_default_encoding_conversion(s, len, false);
// 通用情况...在事务外部不起作用
// 调用 pg_do_encoding_conversion 函数,将字符串从数据库编码转换为目标编码并返回结果
return (char *) pg_do_encoding_conversion((unsigned char *) s,
len,
DatabaseEncoding->encoding,
encoding);
}
在 PostgreSQL 中,pg_server_to_any 和 pg_any_to_server 是两个用于处理编码转换的内部函数。这些函数主要用于在服务器编码和任何其他指定编码之间转换文本数据。虽然在标准的 PostgreSQL 安装和文档中,这些函数通常不会直接暴露给最终用户,了解它们的作用对于理解 PostgreSQL 如何处理不同编码的数据仍然很重要。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。