源码链接
概述
该cpp文件中仅有 ScanKeywordLookup() 这一个函数,但却是在词法分析中使用最频繁的一个函数,在将一个SQL语句划分为多个 token 的过程中起着至关重要的作用。
源码
//代码清单1
const ScanKeyword* ScanKeywordLookup(const char* text, const ScanKeyword* keywords, int num_keywords)
{
int len, i;
char word[NAMEDATALEN] = {0};
const ScanKeyword* low = NULL;
const ScanKeyword* high = NULL;
if (text == NULL) {
return NULL;
}
len = strlen(text);
/* We assume all keywords are shorter than NAMEDATALEN. */
if (len >= NAMEDATALEN) {
return NULL;
}
/*
* Apply an ASCII-only downcasing. We must not use tolower() since it may
* produce the wrong translation in some locales (eg, Turkish).
*/
for (i = 0; i < len; i++) {
char ch = text[i];
if (ch >= 'A' && ch <= 'Z') {
ch += 'a' - 'A';
}
word[i] = ch;
}
word[len] = '\0';
/*
* Now do a binary search using plain strcmp() comparison.
*/
low = keywords;
high = keywords + (num_keywords - 1);
while (low <= high) {
const ScanKeyword* middle = NULL;
int difference;
middle = low + (high - low) / 2;
difference = strcmp(middle->name, word);
if (difference == 0) {
return middle;
} else if (difference < 0) {
low = middle + 1;
} else {
high = middle - 1;
}
}
return NULL;
}
解析
可以看到,ScanKeywordLookup() 有三个参数,分别为 text、keywords、num_keywords。第一个参数 text 是一个常量指针,指向一个存储了关键字或标识符的字符串;第二个参数 keywords 是一个 ScanKeyword* 类型的常量指针,指向一个储存了包括所有系统支持的关键字信息在内的结构体数组;第三个参数 num_keywords 正是这个结构体数组所含有的元素个数。
在代码清单1第5行,定义了一个字符数组,当把 text[] 数组中的英文字母字符均变为其小写形式后,用它来存储这个新的字符串,第23~31行的代码执行的作用正是这个:
//代码清单2
for (i = 0; i < len; i++) {
char ch = text[i];
if (ch >= 'A' && ch <= 'Z') {
ch += 'a' - 'A';
}
word[i] = ch;
}
word[len] = '\0';
而正如,我们在这篇博客中提到的:https://blog.csdn.net/m0_60340015/article/details/126395890,由于某些地区的语言体系不同,所以不能直接用 tolower() 函数来将单个字符转化为其对应小写形式。
代码清单1第36~51行代码,执行了在给定的有序的关键字数组中二分匹配关键字的功能。在第36和37行,分别对应数组的第一个和最后一个元素。第42行不断地缩小一般的范围,然后根据strcmp() 的返回值确定返回值。
ScanKeyword结构体如下,所在文件的路径为:src/include/parser/keywords.h
//代码清单3
typedef struct ScanKeyword {
const char* name; /* in lower case */
int16 value; /* grammar's token code */
int16 category; /* see codes above */
} ScanKeyword;
在这个文件中我们也可以看到,关键字是分类别的,它们应当能够与 gram.y 中的列表匹配:
//代码清单4
#define UNRESERVED_KEYWORD 0
#define COL_NAME_KEYWORD 1
#define TYPE_FUNC_NAME_KEYWORD 2
#define RESERVED_KEYWORD 3
而所有的关键字又按照字典序被排列,所在文件的路径为:src/include/parser/kwlist.h,当匹配成功后,该函数就会返回当前标识符指向单词表中对应单词的指针。
//代码清单5
/* name, token-value, category */
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD)
PG_KEYWORD("account", ACCOUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD)
PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD)
PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD)
PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD)
PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD)
PG_KEYWORD("algorithm", ALGORITHM, UNRESERVED_KEYWORD)
PG_KEYWORD("all", ALL, RESERVED_KEYWORD)
PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD)
PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD)
PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD)
PG_KEYWORD("analyse", ANALYSE, RESERVED_KEYWORD) /* British spelling */
PG_KEYWORD("analyze", ANALYZE, RESERVED_KEYWORD)
PG_KEYWORD("and", AND, RESERVED_KEYWORD)
PG_KEYWORD("any", ANY, RESERVED_KEYWORD)
······
总结
该函数之所以是使用最频繁的一个函数,在于我们需要将 SQL 语句的每一个用空格分隔的字符串都用该函数去匹配一下是不是关键字,而当用该函数确认所有 token 后,接下来就可以作进一步的语法分析了。