源码链接
概述
这个cpp文件在src/common/backend/parser目录下,它提供了词法分析中的常用函数,这些函数被用来处理查询语句中的转义字符。在这篇博客里,我将介绍文件中常常一起使用的两个函数downcase_truncate_identifier() 和 truncate_identifier() 。
源码
//代码清单1
char* downcase_truncate_identifier(const char* ident, int len, bool warn)
{
char* result = NULL;
int i;
bool enc_is_single_byte = false;
result = (char*)palloc(len + 1);
enc_is_single_byte = pg_database_encoding_max_length() == 1;
for (i = 0; i < len; i++) {
unsigned char ch = (unsigned char)ident[i];
if (ch >= 'A' && ch <= 'Z') {
ch += 'a' - 'A';
} else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) {
ch = tolower(ch);
}
result[i] = (char)ch;
}
result[i] = '\0';
if (i >= NAMEDATALEN)
truncate_identifier(result, i, warn);
return result;
}
void truncate_identifier(char* ident, int len, bool warn)
{
if (len >= NAMEDATALEN) {
len = pg_mbcliplen(ident, len, NAMEDATALEN - 1);
if (warn) {
char buf[NAMEDATALEN];
errno_t rc;
rc = memcpy_s(buf, NAMEDATALEN, ident, len);
securec_check(rc, "\0", "\0");
buf[len] = '\0';
ereport(NOTICE,
(errcode(ERRCODE_NAME_TOO_LONG), errmsg("identifier \"%s\" will be truncated to \"%s\"", ident, buf)));
}
ident[len] = '\0';
}
}
解析
函数downcase_truncate_identifier()
我们先来讲一下第一个函数 downcase_truncate_identifier() 的作用,顾名思义,它是要将一个标识符进行降格和截断处理。那什么是降格呢?其实就是将标识符中的大写英文字母变为小写。那截断需要什么条件吗?当然,如果标识符的长度超过了规定的最大长度,那么才会截断,这个最大长度即宏常量NAMEDATALEN,即64(实际为63,最后一位为 ‘\0’ )。
downcase_truncate_identifier() 有三个参数,第一个参数为一个指向包含了标识符常量字符串的指针,第二个参数为该字符串的长度,第三个参数用于标识是否在字符串长度大于规定长度时输出警告信息。
在代码清单1第4行我们定义了一个字符串指针,并在第8行为它分配了足够的空间。在第6行我们定义了boolean变量用于标明是单字节还是多字节编码,默认是多字节编码,所以我们在第9行通过 pg_database_encoding_max_length() 函数获取字节编码的方式,如果它的返回值为1,自然就将这个boolean类型的变量 enc_is_single_byte 的值赋为 true 。
第10~19行的for循环结构是这个函数的核心,大部分情况下,我们执行第二个块中的判断语句即可:
//代码清单2
else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) {
ch = tolower(ch);
}
这时,我们需要了解一下 IS_HIGHBIT_SET() 函数:
//代码清单3
#define IS_HIGHBIT_SET(ch) ((unsigned char)(ch) & HIGHBIT)
#define HIGHBIT (0x80)
在ASCII码中,某个英文字母 ch 与 HIGHBIT 的逻辑与的结果必为零,但是当编码方式不是ASCII码,而是其它某个地区特有的编码方式时,这段代码就起到作用了。
可是,这并非是一定的,比如在土耳其语中 'i' 和 'I' 并非是配套的大小写字母,它们有各自的大写字母,分别为 'İ' 和 'L' ,所以对应的编码不再是ASCII码了,这时用的便是另外一个块中的语句将其转换为小写:
//代码清单4
if (ch >= 'A' && ch <= 'Z') {
ch += 'a' - 'A';
}
当然,代码清单4中的代码已经够用了。
第22行的语句判断目标字符串的的长度(这里包括了 '\0' )是否超过规定的最大长度NAMEDATALEN,注意是超过而非等于NAMEDATALEN,这是因为数组下标是从0开始的,如果确实超过了,那么继续调用 truncate_identifier() ,对目标字符串作进一步的截断处理。
函数truncate_identifier()
在代码清单1第30行,len 变量的值取 pg_mbcliplen(ident, len, NAMEDATALEN - 1) 的返回值即 NAMEDATALEN - 1 。另外,如果 warn 变量为 true ,那么将执行截断操作的信息打印出来,在第35行进行安全性检查,确保如果在分配内存时发生错误会抛出异常。
总结
总的来说,downcase_truncate_identifier() 函数的作用就是将标识符中的大写字母转化为对应的小写字母,只有当标识符的长度过长时,它才会调用 truncate_identifier() 函数进行截断,这一点是需要明确的。