技术贴 | SQL 编译与执行 -parser

news2024/11/16 8:45:42

前言

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 根据给定的规则,生成符合需求的词法分析器和语法分析器。一般这两种配置都是文本文件且结构相同:


... definitions ...
%%
... rules ...
%%
... subroutines …

复制代码

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


...
%%
/* 变量 */
[a-z]    {
            yylval = *yytext - 'a';
            return VARIABLE;
         }  
/* 整数 */
[0-9]+   {
            yylval = atoi(yytext);
            return INTEGER;
         }
/* 操作符 */
[-+()=/*\n] { return *yytext; }
/* 跳过空格 */
[ \t]    ;
/* 其他格式报错 */
.        yyerror("invalid character");
%%
…

复制代码

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


%token INTEGER VARIABLE
%left '+' '-'
%left '*' '/'
...
%%
program:
        program statement '\n'
        |
        ;


statement:
        expr                    { printf("%d\n", $1); }
        | VARIABLE '=' expr     { sym[$1] = $3; }
        ;
       
expr:
        INTEGER
        | VARIABLE              { $$ = sym[$1]; }
        | expr '+' expr         { $$ = $1 + $3; }
        | expr '-' expr         { $$ = $1 - $3; }
        | expr '*' expr         { $$ = $1 * $3; }
        | expr '/' expr         { $$ = $1 / $3; }
        | '(' expr ')'          { $$ = $2; }
        ;
%%
…

复制代码

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

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

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


// 有引号的代表终结符,没有的代表非终结符。
SelectStmt:
             // 代表从哪些表里获取到哪些字段
         SelectFiled FromTable
SelectFiled:
           // FieldList 代表着一个字段列表
           “Select” FieldList
          | “Select” “*”
FromTable:
             // 从一个表列表中获取
            “From” TableList
FieldList:
           // FieldList 可以是某个字段,也可以是多个字段,利用递归可以扩展到无数字段
          “Field”
         | FieldList “,” “Field”
TableList:
          // TableList同理
           “Table”
         | TableList “,” “Table”

复制代码

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

     SELECT . * FROM test
// 匹配到终结符SELECT,继续执行
→   SELECT * . FROM test
// 此时堆栈里的内容匹配到 SelectFiled,将 SELECT *弹出,SelectFiled 压入到堆栈
→   SelectFiled . FROM test
→   SelectFiled FROM . test
→   SelectFiled FROM test .
→   SelectFiled FROM TableList .
→   SelectFiled FromTable .
→   SelectStmt

复制代码

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

三、SQL parser

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

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

%union {
  id    int32
  pos   int32
  str   string
  union sqlSymUnion
}

复制代码

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

%token <str> IDENT SCONST BCONST BITCONST
…
%type <tree.Statement> stmt_block
%type <tree.Statement> stmt
…
%left      AND
%right     NOT
%left      AND_AND
%nonassoc  IS ISNULL NOTNULL 
%nonassoc  '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
…
%%

复制代码

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

create_table_stmt:
  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
  {
    name := $4.unresolvedObjectName().ToTableName()
    $$.val = &tree.CreateTable{
      Table: name,
      IfNotExists: false,
      Interleave: $8.interleave(),
      Defs: $6.tblDefs(),
      AsSource: nil,
      PartitionBy: $9.partitionBy(),
      Temporary: $2.persistenceType(),
      StorageParams: $10.storageParams(),
      OnCommit: $11.createTableOnCommitSetting(),
    }
  }
| 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
  {
    name := $7.unresolvedObjectName().ToTableName()
    $$.val = &tree.CreateTable{
      Table: name,
      IfNotExists: true,
      Interleave: $11.interleave(),
      Defs: $9.tblDefs(),
      AsSource: nil,
      PartitionBy: $12.partitionBy(),
      Temporary: $2.persistenceType(),
      StorageParams: $13.storageParams(),
      OnCommit: $14.createTableOnCommitSetting(),
    }
  }

复制代码

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

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

type CreateTable struct {
   IfNotExists   bool
   Table         TableName
   Interleave    *InterleaveDef
   PartitionBy   *PartitionBy
   Temporary     bool
   StorageParams StorageParams
   OnCommit      CreateTableOnCommitSetting
   Defs     TableDefs
   AsSource *Select
}

复制代码

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

总结

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

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136842.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十四、TCP多线程、原子类AtomicInteger、日志、枚举

tcp多线程 tcp客户端 多线程收发代码 package com.heima.test2;import java.io.*; import java.net.Socket; import java.nio.charset.Charset; import java.util.Scanner;class ClientSend implements Runnable {Socket socket;Scanner sc new Scanner(System.in);public C…

2019年数维杯国际大学生数学建模B题无人机避障问题设计规划求解全过程文档及程序

2019年数维杯国际大学生数学建模 B题 无人机避障问题设计规划 问题重述&#xff1a; 任务1&#xff1a;假设无人机在飞行过程中不受风向、湿度等外界因素的影响&#xff0c;飞行速度和拍摄角度恒定&#xff0c;无人机对一定宽度的区域进行直线飞行模式航拍。执行此航拍的飞行…

SpringBoot(一): SpringBoot的创建和使用

Spring的创建和使用1. 什么是Spring&#xff1f;2. SpringBoot的优点3. SpringBoot项目的创建3.1 使用IDEA创建3.2 使用网页创建4. 项目目录介绍和运行4.1 目录介绍4.2 项目运行4.3 输出hello world4.4 约定大于配置1. 什么是Spring&#xff1f; Spring的诞生是为了简化Java程…

Spring-boot启动失败 Unregistering JMX-exposed beans on shutdown 异常处理

目录一、异常错误二、原因三、解决方法一、异常错误 Spring-boot启动Run时&#xff0c;出现 o.s.j.e.a.AnnotationMBeanExporter - Unregistering JMX-exposed beans on shutdown 错误 *************************** APPLICATION FAILED TO START Description: The Tomcat conn…

【小程序】包与数据共享

文章目录使用 npm 包Vant WeappAPI Promise化全局事件共享MobX分包分包概念使用分包独立分包分包预下载使用 npm 包 目前&#xff0c;小程序中已经支持使用 npm 安装第三方包&#xff0c;从而来提高小程序的开发效率。但是&#xff0c;在小程序中使用npm 包有如下 3 个限制&am…

【韩顺平Linux】学习笔记3

【韩顺平Linux】学习笔记3一、文件目录指令pwd指令 ls指令cd指令mkdir指令rmdir指令touch指令cp指令rm指令mv指令cat指令more指令less 指令echo指令 head指令tail指令> 指令 >>指令ln指令history指令二、时间日期指令三、查找指令四、压缩和解压一、文件目录指令 根目…

【前端】Vue项目:旅游App-(3)TabBar:点击active效果、点击路由跳转

文章目录目标代码与过程设置active主题颜色添加点击active效果点击路由跳转效果总代码修改或新增的文件common.cssindex.csstab-bar.vue目标 添加点击active效果实现点击路由跳转效果 上一篇TabBar搭建&#xff1a;【前端】Vue项目&#xff1a;旅游App-&#xff08;2&#xff…

LVGL学习笔记12 - 复选框CheckBox

目录 1. Parts 1.1 LV_PART_MAIN 1.2 LV_PART_INDICATOR 2. 状态 3. 样式 3.1 设置字符串颜色 3.2 设置点击框外框颜色 3.3 修改点击框弧度 3.4 修改字符串与点击框的间隔 4. 事件 复选框通过lv_checkbox_create创建。一个CheckBox由一个点击框加一个Label组成。 obj1 …

Minikube Mac 安装 使用

Minikube Mac 安装 使用 环境要求 硬件要求 至少 2核 CPUs2GB 以上内存20GB 以上磁盘空间网络环境容器或虚拟机, 例如: Docker, QEMU, Hyperkit, Hyper-V, KVM, Parallels, Podman, VirtualBox, or VMware Fusion/Workstation 本机环境 Mac Pro 10.13.6 Docker 18.09.1 …

半导体行业相关术语

目录 1.晶圆&#xff08;wafer&#xff09; 2. 自动化测试设备&#xff08;ATE Automatic Test Equipment&#xff09; 3.晶盒&#xff08;Cassette&#xff09; 4. 待测设备(DUT Device Under Test) 5. 探针接口板(PIB Prober Interface Board) 6. 设备接口板(DIB D…

干货 | web自动化总卡在文件上传和弹框处理上?

在有些场景中&#xff0c;需要上传文件&#xff0c;而 Selenium 无法定位到弹出的文件框&#xff0c;以及网页弹出的提醒。这些都是需要特殊的方式来处理。input 标签使用自动化上传&#xff0c;先定位到上传按钮&#xff0c;然后 send_keys 把路径作为值给传进去.如图所示&…

【计算机网络-物理层】通信基础

文章目录1 码元、速率、波特、带宽1.1 码元1.2 波特率1.3 比特率1.4 带宽1.5 相关例题2 奈氏准则、香农定理2.1 奈氏准则&#xff08;采样定理&#xff09;2.2 香农定理2.3 相关例题3 编码方式3.1 归零编码&#xff08;RZ&#xff09;3.2 非归零编码&#xff08;NRZ&#xff09…

【简单DP】[NOIP2007 普及组] 守望者的逃离

P1095 [NOIP2007 普及组] 守望者的逃离 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)题意&#xff1a;思路&#xff1a;独立做出来的一道DP&#xff01;一开始我去模拟过程找子问题&#xff0c;然后去找阶段是什么本来想的是以路程作为阶段&#xff0c;但是1e8数组开不下那么…

如何看待PyTorch 2.0?

作者&#xff5c;吴育昕 1 为什么是TorchDynamo Graph capture 把用户 Python 写的模型代码变成 graph&#xff0c;是一切编译的根基。而 PyTorch 在试了这么多方案之后似乎已经锁定 TorchDynamo 作为 graph capture 的未来方向了&#xff0c;所以写一点关于 TorchDynamo 的…

假如面试官问你Babel的原理该怎么回答

1. 什么是 Babel 简单地说&#xff0c;Babel 能够转译 ECMAScript 2015 的代码&#xff0c;使它在旧的浏览器或者环境中也能够运行。 // es2015 的 const 和 arrow function const add (a, b) > a b;// Babel 转译后 var add function add(a, b) {return a b; };Babel…

pwr | 谁说样本量计算是个老大难问题!?(二)(独立样本均值篇)

1写在前面 上次介绍了两组发生率的样本量计算方法&#xff0c;通过pwr包进行计算非常简单&#xff0c;可以有效地减少我们的工作量。&#x1f618; 有时候我们想比较两组之间的均值&#xff0c;如何计算样本量又一次成了老大难问题。&#x1f912; 本期我们还是基于pwr包&#…

【自学Java】Windows安装PyCharm IDE

Windows安装PyCharm IDE PyCharm下载 PyCharm下载地址 https://www.jetbrains.com/pycharm/PyCharm下载 打开上面的链接&#xff0c;打开 Python 的开发工具 PyCharm 的下载页面&#xff0c;如下图所示&#xff1a; 这里我们点击 Download&#xff0c;跳转到新的页面&#…

错过短视频,微博奔向新浪

以后新浪或许会被叫做“微博新浪”。 2022年12月23日晚&#xff0c;港股微博发布公告称&#xff0c;拟斥资15亿元收购新浪网技术有限公司100%股权。此举被外界解读为微博将反向收购新浪。 曾经&#xff0c;微博还是新浪移动互联网时代的“船票”。随着门户网站逐渐凋零&#…

基于ODX/OTX诊断的整车扫描

| ODX (Open Diagnostic data eXchange) 是基于XML语言、开放的诊断数据格式&#xff0c;用于车辆整个生命周期中诊断数据的交互。它一开始由ASAM提出并形成标准MCD-2D&#xff0c;后来以ODX2.2.0为基础形成了ISO标准——ISO 22901-1。 | OTX (Open Test sequence eXchange) …

Redis主从复制哨兵模式

Redis主从复制&哨兵模式一 什么是Redis主从复制1.1 主从复制的架构1.2 主从复制的原理1.3 主库是否要开启持久化1.4 辅助配置&#xff08;主从数据一致性配置&#xff09;二 主从复制配置2.1 slave 命令2.2 配置文件三 主从复制常见问题四 Redis哨兵机制4.1 什么是哨兵模式…