当前位置:   article > 正文

SQL 编译与执行 -parser_sqlparser

sqlparser

前言

SQL 编译与执行系列技术博客将按照以下顺序分别介绍整个 SQL 执行引擎。

图一 SQL 编译与执行研读流程

  • parser 部分,包括词法解析和语法解析。

  • compile 部分,包括语义解析以及计划的构建。

  • optimize 部分,包括计划的优化。

  • exec 部分,包括执行计划的生成以及执行。

本文作为本系列第一篇文章,首先为大家介绍 parser 的核心设计,主要包括 SQL 词法以及语法的解析。

一、SQL 执行流程

图二所示为一条 SQL 语句的整体处理流程,总体来说,一条 SQL 语句需要经过下述步骤:parser—构建逻辑计划—构建优化计划—构建物理计划—构建执行计划。

图二 SQL 执行流程

parser 过程最主要的目的就是解析输入的 SQL 语句,通过词法解析器解析为 token,通过语法解析器生成抽象语法树,而后即可传入 SQL 执行引擎进行识别和处理。

接着进入下一步,首先要对输入的数据进行有效性验证,解析并转换 AST 树,构建逻辑计划。接下来就是对生成的计划进行优化以找到代价最小的执行方式,根据优化好的逻辑计划构建可执行的物理计划,最后下发到各个节点进行分布式执行。

二、Lex & Yacc

Lex 代表 Lexical Analyzar(词法分析器),Yacc 代表 Yet Another Compiler Compiler(编译器代码生成器)。它们分别是用来生成词法分析器和语法分析器的工具,Lex 和 Yacc 在 UNIX 下分别叫 Flex 和 Bison。

词法解析是编译的第一步,将语句拆分为 token,移除空格和注释,逐个读入源码中的 character,检查是否有效并传递给语法分析器。语法分析器以 token-stream 的形式从词法分析器获取输入;解析器根据规则文件生成的规则将 token 解析成一个抽象语法树的结构,如图三所示。

图三 Lex & Yacc 执行流程

从上图的执行流程可以看出,我们需要分别为 Lex 和 Yacc 提供对应的规则文件,Lex & Yacc 根据给定的规则,生成符合需求的词法分析器和语法分析器。一般这两种配置都是文本文件且结构相同:

  1. ... definitions ...
  2. %%
  3. ... rules ...
  4. %%
  5. ... subroutines …

复制代码

文件通过 %% 分成三部分,最上面定义了各种名称,例如各种表达式、token 等,中间则是重点的规则,首先看一下 Lex 的规则文件:

  1. ...
  2. %%
  3. /* 变量 */
  4. [a-z] {
  5. yylval = *yytext - 'a';
  6. return VARIABLE;
  7. }
  8. /* 整数 */
  9. [0-9]+ {
  10. yylval = atoi(yytext);
  11. return INTEGER;
  12. }
  13. /* 操作符 */
  14. [-+()=/*\n] { return *yytext; }
  15. /* 跳过空格 */
  16. [ \t] ;
  17. /* 其他格式报错 */
  18. . yyerror("invalid character");
  19. %%

复制代码

对于词法解析对应的规则,可以看出,左边是扫出来的字符内容,通过正则表达式进行匹配,如果匹配到即返回右边大括号中运行的结果。再看看 Yacc 对应的规则文件:

  1. %token INTEGER VARIABLE
  2. %left '+' '-'
  3. %left '*' '/'
  4. ...
  5. %%
  6. program:
  7. program statement '\n'
  8. |
  9. ;
  10. statement:
  11. expr { printf("%d\n", $1); }
  12. | VARIABLE '=' expr { sym[$1] = $3; }
  13. ;
  14. expr:
  15. INTEGER
  16. | VARIABLE { $$ = sym[$1]; }
  17. | expr '+' expr { $$ = $1 + $3; }
  18. | expr '-' expr { $$ = $1 - $3; }
  19. | expr '*' expr { $$ = $1 * $3; }
  20. | expr '/' expr { $$ = $1 / $3; }
  21. | '(' expr ')' { $$ = $2; }
  22. ;
  23. %%

复制代码

首先是定义了 token 以及一些运算符。%left 代表左结合,同一行的运算符优先级是一样的,不同行的越靠下优先级就越高。

语法规则使用了巴科斯范式(BNF)定义,它不仅能严格地表示语法规则,而且所描述的语法是与上下文无关的。它具有语法简单,表示明确,便于语法分析和编译的特点。每条规则的左部是一个非终结符,右部是由非终结符和终结符组成的一个符号串。具有相同左部的规则可以共用一个左部,各右部之间以直竖“|”隔开。

解析表达式是生成表达式的逆向操作,我们需要归宿表达式到一个非终结符。Yacc 生成的语法分析器使用自底向上的归约(shift-reduce)方式进行语法解析,同时使用堆栈保存中间状态。简单的以一条 select 为例:

  1. // 有引号的代表终结符,没有的代表非终结符。
  2. SelectStmt:
  3. // 代表从哪些表里获取到哪些字段
  4. SelectFiled FromTable
  5. SelectFiled:
  6. // FieldList 代表着一个字段列表
  7. “Select” FieldList
  8. | “Select” “*”
  9. FromTable:
  10. // 从一个表列表中获取
  11. “From” TableList
  12. FieldList:
  13. // FieldList 可以是某个字段,也可以是多个字段,利用递归可以扩展到无数字段
  14. “Field”
  15. | FieldList “,” “Field”
  16. TableList:
  17. // TableList同理
  18. “Table”
  19. | TableList “,” “Table”

复制代码

当语法分析器进行语法分析的时候,用 . 代表当前读取到的位置,以 SELECT * FROM test 为例:

  1. SELECT . * FROM test
  2. // 匹配到终结符SELECT,继续执行
  3. → SELECT * . FROM test
  4. // 此时堆栈里的内容匹配到 SelectFiled,将 SELECT *弹出,SelectFiled 压入到堆栈
  5. → SelectFiled . FROM test
  6. → SelectFiled FROM . test
  7. → SelectFiled FROM test .
  8. → SelectFiled FROM TableList .
  9. → SelectFiled FromTable .
  10. → SelectStmt

复制代码

通过一系列的转换,我们就获得了一个 SelectStmt,而整个过程就可以构造一棵树,用于 SQL 解析。上述所示仅为一个简单的例子,真实使用的结构则会复杂的多。

三、SQL parser

KaiwuDB 使用了 Goyacc 生成语法分析器,而 Lex 则是手写出来的,实现了 Goyacc 中要求的接口,对应 sql/pkg/sql/parser/scan.go pkg/sql/parser/lexer.go,实现了词法分析的功能。

语法分析器所对应的功能在 sql.y 文件下。该文件仍符合上文所述 Yacc 规则文件格式,但没有第三部分 subroutines,第一部分主要就是对一些 token、表达式、优先级、结合性的定义,其中有一个 union 结构体。

  1. %union {
  2. id int32
  3. pos int32
  4. str string
  5. union sqlSymUnion
  6. }

复制代码

该结构体会在 sql.go 生成文件里面生成一个对应的结构体,主要用来定义表达式和 token 的类型,存放解析过程中 token 的相关变量信息以及最后生成的 AST 信息。此外,还有一些对 token(终结符)和表达式(非终结符)的定义。

  1. %token <str> IDENT SCONST BCONST BITCONST
  2. %type <tree.Statement> stmt_block
  3. %type <tree.Statement> stmt
  4. %left AND
  5. %right NOT
  6. %left AND_AND
  7. %nonassoc IS ISNULL NOTNULL
  8. %nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
  9. %%

复制代码

下面是关于 rule 的定义,以 create table 为例:

  1. create_table_stmt:
  2. CREATE opt_temp_create_table TABLE table_name '(' opt_table_elem_list ')' opt_interleave opt_partition_by opt_table_with opt_create_table_on_commit
  3. {
  4. name := $4.unresolvedObjectName().ToTableName()
  5. $$.val = &tree.CreateTable{
  6. Table: name,
  7. IfNotExists: false,
  8. Interleave: $8.interleave(),
  9. Defs: $6.tblDefs(),
  10. AsSource: nil,
  11. PartitionBy: $9.partitionBy(),
  12. Temporary: $2.persistenceType(),
  13. StorageParams: $10.storageParams(),
  14. OnCommit: $11.createTableOnCommitSetting(),
  15. }
  16. }
  17. | CREATE opt_temp_create_table TABLE IF NOT EXISTS table_name '(' opt_table_elem_list ')' opt_interleave opt_partition_by opt_table_with opt_create_table_on_commit
  18. {
  19. name := $7.unresolvedObjectName().ToTableName()
  20. $$.val = &tree.CreateTable{
  21. Table: name,
  22. IfNotExists: true,
  23. Interleave: $11.interleave(),
  24. Defs: $9.tblDefs(),
  25. AsSource: nil,
  26. PartitionBy: $12.partitionBy(),
  27. Temporary: $2.persistenceType(),
  28. StorageParams: $13.storageParams(),
  29. OnCommit: $14.createTableOnCommitSetting(),
  30. }
  31. }

复制代码

可以看出,除了上述所说的一些终结符和非终结符外,还有一个大括号,大括号里面的内容就是当匹配时进行的一些操作,主要就是构建出所需要的 AST。

其中 1对应的就是匹配到的第一个字符,4 就是 table_name 这一部分,最后产生的 CreateTable 这个结构体就对应着 tree 包下的结构体。

  1. type CreateTable struct {
  2. IfNotExists bool
  3. Table TableName
  4. Interleave *InterleaveDef
  5. PartitionBy *PartitionBy
  6. Temporary bool
  7. StorageParams StorageParams
  8. OnCommit CreateTableOnCommitSetting
  9. Defs TableDefs
  10. AsSource *Select
  11. }

复制代码

通过生成的 sql.go 中的 parse 就可以将 token-stream 生成一个 AST 对应的结构。

总结

以上就是 KaiwuDB 的 SQL parser 词法解析和语法解析部分,主要是语法解析部分使用 Goyacc 工具将 sql.y 中的规则生成对应的语法分析器,将词法分析器生成的 token-stream 解析成制定好的树结构。具备这些基础后,我们就可以进行语法的添加以及修改,增加更多的解析规则,为后续操作做好准备。

END

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/696065
推荐阅读
相关标签
  

闽ICP备14008679号