引言
在之前的工作中,我对openGauss SQL引擎的查询解析(parser)模块进行了学习并对相关的多个源文件进行了评注和解读;截至目前,针对parser模块我已经撰写了15篇学习博客,涉及十余个源代码文件的文件内容、函数细节、结构定义和调用关系的解析和总结。在本篇博客中,我对目前学习所得进行简要总结。
对用户输入查询语句的解析过程可以大致分为三个步骤,即词法解析、语法解析和语义解析,其中词法解析与语法解析都属于查询语句的原始解析(Raw Parse)过程。在本篇博客中,我将查询解析分为原始解析和语义解析两个方面,对我个人的工作进展和学习所得做以说明。另外,上述的划分仅是从查询解析工作流程的概念层面进行粗略的划分;而通过对源代码的学习,我了解到,查询的原始解析和语义解析并非是完全独立的过程,它们之间其实有着千丝万缕的联系。
文件路径
opengauss-server/src/common/backend/parser
原始解析
原始解析分为词法和语法的解析过程。总结来讲,词法解析是从查询语句中识别出系统支持的关键字、标识符、运算符、终结符等,确定每个词固有的词性;而语法分析是根据SQL的标准定义语法规则,使用词法分析中产生的词去匹配语法规则,如果一个 SQL 语句能够匹配一个语法规则,则生成对应的抽象语法树(Abstract Syntax Tree,AST)。
说地简单些,语法分析会根据词法分析获得的词(tokens)来匹配语法规则,最终生成一个抽象语法树。语法树是如上用以表现编程语言语法结构的一种树状结构,它的每个结点都代表源代码中的一种结构或组成部分。
对于词法分析部分,我学习了基本原始解析工具flex&bison的工作原理,对词法定义文件scan.l的内容做了简要解读,并对与关键词识别和处理相关的文件kwlookup.cpp、keywords.cpp,与转义字符处理相关的文件scansup.cpp及部分头文件的文件内容、结构定义、函数细节和调用关系进行了详细解读和总结。通过这些学习,我对词法解析的工作流程和各文件之间的调用关系有了基本的理解和认识。
SQL parser解读(2)—— 原始解析工具flex&bison
SQL parser解读(3)—— 对flex源文件scan.l的简单解析
SQL parser解读(5)—— 词法解析中转义字符的处理,scansup.cpp解析(上)
SQL parser解读(6)—— 词法解析中转义字符的处理,scansup.cpp解析(下)
对于语法解析部分,我重新对原始解析主流程函数raw_parser所在的parser.cpp的文件内容和部分函数的逻辑细节进行了学习和解读,在这之后我对与语法处理过程中操作符识别和处理相关的文件parse_oper.cpp,与参数处理相关的文件parse_param.cpp的结构定义和函数细节进行了详细的学习和解读。通过这些学习,我对原始解析的整体工作流程、结构定义以及各模块之间的调用关系有了较为系统的认识。
SQL parser解读(7)—— parser.cpp解读(上)
SQL parser解读(8)—— parser.cpp解读(下)
SQL parser解读(9)—— parse_oper.cpp 解析(上)
SQL parser解读(10)—— parse_oper.cpp 解析(下)
SQL parser解读(11)—— parse_param.cpp解析
通过上述内容的学习,我将原始解析部分的工作流程、函数之间的调用以及各文件之间的生成和调用关系总结如下图:
语义解析
简单来讲,语义解析会对语法解析(原始解析)生成的抽象语法树AST进行语义分析和扩充,并且得到最原始的查询树(Query Tree,QT)。得到查询树后,parser就完成了它的工作;而所得的查询树还会通过重写模块(rewriter)、查询优化(optimizer)等对其进行优化和重构,最终得到的优化后的查询树交由执行模块(executor)执行查询任务。
语义解析部分有两个十分重要的结构体,ParseState和Query;前者是语义解析过程的中间变量结构,它在每次语义解析开始时被创建,在解析工作完成后,它的生命周期就会结束。它由语义解析主流程函数parse_analyze(或parse_analyze_varparams)创建,并贯穿整个语义解析过程;它用于保存语义解析的中间信息,实现抽象语法树向查询树之间的转化。而后者是查询树的结构定义,其保存语义解析所得查询树的细节。一条SQL语句的每个子句的语义分析结果都会保存在Query的对应字段中。
语义解析部分的函数执行和调用逻辑是十分清晰的,其大部分工作都包含在文件analyze.cpp中。关于语义解析部分我主要对analyze.cpp中的结构和部分函数进行了详细的学习和解读。
SQL parser解读(12)—— analyze.cpp解析(上)
SQL parser解读(13)—— analyze.cpp解析(下)
通过上述学习,我对语义解析的工作流程、过程中存在的函数递归调用关系和查询树自底向上的构造过程有了较为清晰的理解和认识。我将语义解析过程中所蕴含的调用逻辑和函数递归总结如下图:
总结
通过这么多天的学习,我对openGauss SQL引擎中的查询解析parser部分形成了比较清晰的认识和知识架构,对相关源代码的文件内容、函数细节和各模块的调用关系也进行了比较详实的学习和解读。回到最初的起点,我将openGauss SQL parser的模块划分和几个主要函数、文件之间的调用关系总结如下图,同时也标志着我完成了对parser模块的学习和解读工作。
收获不仅仅是知识上的,从最开始的手忙脚乱,惊叹于openGauss体系结构之庞大以及专业开发人员考虑问题之周到详尽,到后来的渐入佳境,自己也愈发有信心将工作进行下去,这样的经历才是弥足珍贵的。截至目前的学习已让我受益良多,不过只学习一个模块的知识难免是不成体系的;我还会对openGauss SQL引擎中的其他模块进行学习和解读工作,相信之后的学习一定能够获得更大的收获。