说啥都队-喻诗媛个人总结
我是来自广西师范大学说啥都队伍的队员喻诗媛,从参赛开始至比赛结束我一直致力于SQL引擎中的查询解析部分的解读与赏析,即 parser 文件夹。比赛期间,围绕analyze.cpp、parser.cpp、parse_type.cpp等代码细节撰写了十余篇代码解读与赏析博客。
以下是我学习的一些总结
SQL基本形式
SQL语句基本可以分解成下面7大块:
(5)SELECT (6)DISTINCT < select list >
(1)FROM < table source >
(2)WHERE < condition >
(3)GROUP BY < group by list >
(4)HAVING < having condition >
(7) ORDER BY < order by list >
(一)词法分析:字符串分割
(1)Token分类
注释。
关键字(SELECT、CREATE)。
操作符(+、-、>=)。
开闭合标志((、CASE)。
占位符(?)。
空格。
引号包裹的文本、数字、字段。
方言语法(${variable})。
在词法分析阶段,我们的 Tokens 不需要关心关键词是什么,只要识别是不是关键词即可,只要切分即可。因此,在语法分析阶段,才辨别Token是什么关键词;涉及到语义处理时要考虑上下文。
(2)常见分词方法: 正则表达式分词
争对不同方言或Tokens,会有不同的分词函数。因此整体流程如下:
while (sqlStr) {
//这里的函数每取一次 Token,都将取到的 Token 按长度丢掉,继续匹配剩下的字符串,直到字符串被切分完为止
token = getTokenWhitespace(sqlStr, token) | getTokenBlockComment(sqlStr, token);
sqlStr = sqlStr.substring(token.value.length);
tokens.push(token);
}
/*
* 使用普通的 strcmp() 比较进行二分搜索。
*/
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) { /* 找到则返回 middle */
return middle;
} else if (difference < 0) {
low = middle + 1;
} else {
high = middle - 1;
}
}
return NULL; /* 未找到则返回空值,说明该文本中不含关键词 */
(二)语法分析
分析器对词法分析器解析出来的单词(Token)序列在语法上是否满足SQL语法规则。
openGauss中定义了bison工具能够识别的语法文件gram.y,根据SQL语言的不同定义了一系列表达Statement的结构体(这些结构体通常以Stmt作为命名后缀),用来保存语法分析结果。以SELECT查询为例,它对应的Statement结构体如下。
typedef struct SelectStmt
{
NodeTag type;
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
List *valuesLists; /* untransformed list of expression lists */
List *sortClause; /* sort clause (a list of SortBy's) */
Node *limitOffset; /* # of result tuples to skip */
Node *limitCount; /* # of result tuples to return */
……
} SelectStmt;
(三)语义分析
在完成词法分析和语法分析后,parse_Ana lyze函数会根据语法树的类型,调用transformSelectStmt将parseTree改写为查询树。负责语义分析的是位于 analyze.cpp 下的 parse_analyze 函数。parse_analyze 会根据词法分析和语法分析得到的语法树,生成一个 ParseState 结构体用于记录语义分析的状态。ParseState 顾名思义-解析状态,保存了语义分析的状态信息,ParseState 结构体的具体定义如下:
struct ParseState {
struct ParseState* parentParseState; /* 指向外层查询 */
const char* p_sourcetext; /* 原始SQL命令 */
List* p_rtable; /* 范围表 */
List* p_joinexprs; /* 连接表达式 */
List* p_joinlist; /* 连接项 */
List* p_relnamespace; /* 表名集合 */
List* p_varnamespace; /* 属性名集合 */
bool p_lateral_active;
List* p_ctenamespace; /* 公共表达式名集合 */
List* p_future_ctes; /* 不在p_ctenamespace中的公共表达式 */
CommonTableExpr* p_parent_cte;
List* p_windowdefs; /* WINDOW子句的原始定义 */
int p_next_resno; /* 下一个分配给目标属性的资源号 */
List* p_locking_clause; /* 原始的FOR UPDATE/FOR SHARE信息 */
Node* p_value_substitute;
bool p_hasAggs; /* 是否有聚集函数 */
bool p_hasWindowFuncs; /* 是否有窗口函数 */
bool p_hasSubLinks; /* 是否有子链接 */
bool p_hasModifyingCTE;
bool p_is_insert; /* 是否为INSERT语句 */
bool p_locked_from_parent;
bool p_resolve_unknowns;
bool p_hasSynonyms;
Relation p_target_relation; /* 目标表 */
RangeTblEntry* p_target_rangetblentry; /* 目标表在RangeTable对应的项 */
......
};
个人心得
此次比赛是我的一次难忘经历,极大地提升了我的学习分析能力。万事开头难,最开始比赛有点难以下手,不过经过调整静下心来阅读和学习,终于有所收获。收获很多,开心就行
相关博客链接
SQL引擎概览
openGauss:SQL引擎之查询解析中analyze.cpp解读
SQL引擎 - analyze.cpp分析
SQL引擎 - analyze.cpp分析(二)
SQL引擎 - analyze.cpp分析(三)
SQL引擎 - analyze.cpp分析(四)
SQL引擎 - parser.cpp分析
SQL引擎 - parser.cpp分析(二)
SQL引擎 - parse_type.cpp分析(一)
SQL引擎 - parse_type.cpp分析(二)
说啥都队-喻诗媛个人总结