SQL 语句解析过程详解

SQL 语句解析过程详解:

1.输入SQL语句

2.词法分析------flex

使用词法分析器(由Flex生成)将 SQL 语句分解为一个个单词,这些单词被称为"标记"。标记包括关键字、标识符、运算符、分隔符等。

2.1 flex 原理

1、使用 flex 工具定义正则表达式规则来匹配不同类型的词法单元;例如,可以定义以下规则:

  • 匹配关键字:SELECT、FROM、WHERE、HAVING等。
  • 匹配标识符:由字母或下划线开头,后跟字母、数字或下划线组成。
  • 匹配运算符:比如=、<、>、+、等。
  • 匹配常量:包括整数、浮点数、字符串等。

2、生成词法分析器代码:根据定义的词法规则,使用Flex工具生成对应的词法分析器代码;

3、输入查询字符串:将要解析的查询字符串作为输入提供给同法分析器;

4、扫描和匹配:词法分析器从输入字符串中逐个读取字符,并尝试将其与定义的词法规则进行匹配;

5、生成词法单元:当词法分析器匹配到一个词法规则时,它会生成相应的词法单元并返回给语法分析器。每个词法单元通常包含两部分信息:

  • 词法单元类型(token type):表示该词法单元的种类,比如关键字、标识符、运算符等;
  • 词法单元值(tokenvalue):表示该词法单元具体的取值;

6、继续扫描:词法分析器会持续从输入字符串中读取字符,并重复步骤4和步骤5,直到整个查询字符串被完全解析为一系列词法单元;

7、返回词法单元序列:当整个查询字符串都被解析后,词法分析器将返回一个包含所有词法单元的序列给语法分析器,供后续的语法分析处理;

2.2 flex 文件代码结构

2.2.1 flex 文件介绍

1、flex文件代码

cpp 复制代码
%option noyywrap
%{
definition
%}

%%
rules
%%
Code

(1)%option 指定 flex 扫描时的一些特性。yywrap 通常在多文件扫描时定义使用。常用的一些选项有:

  • Noyywrap:告诉flex不使用yywrap函数;
  • yylineno:会告诉flex生成一个名为yylineno的整型变量来保存当前的行号;
  • case-insensitive 正则表达式规则大小写无关;

(2)definitio部分为定义部分,包括引入头文件,变量声明,函数声明,注释等,这部分会被原样拷贝到输出的.c文件中。

(3)rules部分定义词法规则,使用正则表达式定义词法,后面{}内则是扫描到对应词法时的动作代码;"|"是一个特殊符号,表示下一个模式应用相同的动作;正则表达式后面不指定动作,则相应的模式会被忽略。

(4)code部分为C语言的代码。yylex为flex的函数,使用yylex开始扫描。

2.2.2 flex 文件常用变量

(1)yytext:词法分析程序当前识别到的一些词素,与转换规则部分中的某个模式相匹配;

(2)yylength:词法分析程序当前识别到的词素的长度;

(3)yylval:yylval是在bison中定义的联合类型变量(union),因为Flex生成的词法分析程序yylex()需要向bison生成的语法分析器返回识别到的词法单元,所以需要使用yylval来保存词法单元的属性值;

2.2.3 正则表达式

cpp 复制代码
.        匹配除换行符"\n"以外的任何单个字符;

*        匹配前面表达式的零个或多个拷贝;

[]       匹配括号中任意字符的字符类,如果第一个字符是 "^",则匹配除括号中的字符以外的任意字符;"-" 指示一个字符范围,例如"[0-9]"和"[0123456789]"含义相同;除了以 "\" 开始的转义序列,元字符在括号内没有任何含义;

^       作为正则表达式的行首匹配行的开头,也用于方括号中的否定;

$       作为正则表达式的行尾匹配行的结尾;

\        用于转义元字符,也作为常用的C转义序列的一部分,例如"\n"表示换行,"\*" 表示非元字符的星号;

+       匹配前面的正则表达式一次或多次出现;

?      匹配前面的正则表达式零次或一次出现;

|         匹配前面的正则表达式或随后的正则表达式;

"..."  引号中的每个字符解释为字面意义,除C转义序列外元字符会失去其特殊含义;

()      将一系列正则表达式组成一个新的正则表达式,例如(01),表示字符序列 01;

{}      当括号中包含一个或两个数字时,指示前面的模式允许被匹配多少次,例如{1,3}表示匹配字母一次到三次;

2.2.4 flex 文件具体案例

1、创建一个名为 lexer.l 的文件,其中包含词法规则;

cpp 复制代码
%{
#include <stdio.h>
%}

%%
SELECT                  { printf("Keyword: SELECT\n"); }
FROM                    { printf("Keyword: FROM\n"); }
WHERE                   { printf("Keyword: WHERE\n"); }
AND                     { printf("Keyword: AND\n"); }
OR                      { printf("Keyword: OR\n"); }

[0-9]+                  { printf("Number: %s\n", yytext); }

[A-Za-z_][A-Za-z0-9_]*  { printf("Identifier: %s\n", yytext); }
[=><]+                  { printf("Operator: %s\n", yytext); }
[ \t\n]                 ; // Skip whitespace

.                       { printf("Unknown: %s\n",yytext); }
 
%%

int main() {    
    yylex();   
    return 0;
}

2、使用 flex 命令编译 lexer.l 文件,生成词法分析器代码

(1)执行下列语句生成词法分析器代码

cpp 复制代码
flex lexer.l

(2)词法分析器生成结果

cpp 复制代码
lex.yy.c

(3)编译生成的词法分析器代码,生成可执行文件

cpp 复制代码
gcc -o lexer lex.yy.c -lfl

(4)运行可执行文件并输入一些算术表达式进行测试

cpp 复制代码
./lexer

输入:SELECT * FROM table;

(5)执行结果如下

说明:

  • -ll: 这是旧版本的Flex生成器(例如Flex 2.5.4)的链接选项。它指示链接器将使用名为 libl.a 或 libl.so 的库文件。在以前的版本中,Flex生成的词法分析器的默认名称是 lex.yy.c,而库文件的名称以 "l" 开头,因此使用 -ll 是一种传统的方式。
  • -lg: 这是新版本的Flex生成器(例如Flex 2.5.35)的链接选项。类似于旧版本的 -ll,它指示链接器使用名为 libg.a 或 libg.so 的库文件。这种新方式是为了避免与其他工具和库发生命名冲突。
  • -lfl: 这是一个与Flex生成的词法分析器库相关的选项。-lfl 表示链接器将使用名为 libfl.a 或 libfl.so 的库文件。这个库包含了Flex所需的运行时支持函数。

注意:

如果 flex 词法分析器对 .l 进行编译时报错:

/opt/h/devtoolset-11/root/usr/ibexec/gcex86.64-redhat-linux/11/ld: cannot find -lfn

解决方案:

该错误表明链接器无法找到名为 -if 的库文件。这通常是因为在您的系统上缺少libfl库,或者库文件的路径未正确配置。要解决这个问题,您可以尝试以下步骤:

1、确认库是否已安装:首先,请确保您的系统上已安装了libfl库。您可以尝试使用包管理器来安装它。在基于Red Hat的系统中,您可能需要执行类似于以下的命令:

cpp 复制代码
yum install flex-devel

2、检查库文件路径:如果库已安装,但链接器仍然找不到它,可能是因为库文件的路径未正确配置。您可以尝试手动指定库文件的路径。例如,假设libfl库文件位于/usr/lib64目录下,您可以使用以下方式链接:

cpp 复制代码
gcc -o my program lex.yy.c -L/usr/lib64 -1f1

3、更新库文件缓存:如果您最近安装了libfl库,但链接器仍然找不到它,您可能需要更新库文件缓存。运行以下命令以更新库文件缓存:

cpp 复制代码
sudo ldconfig

3.语法分析------bison

使用语法分析器(由 Bison 生成)根据语法规则进行语法分析,生成抽象语法树。语法树是一种树形结构,它表示 SQL 语句的语法结构。语法分析器会检查语法树是否符合 SQL 语法规则,如果不符合,则会抛出语法错误。

3.1 bison原理

3.2 bison文件代码结构

1、bison文件代码

cpp 复制代码
%{
// C 代码和头文件的声明
#include <stdio.h>
// 在这里可以定义全局变量和函数等
%}
// Bison 的选项部分
%option verbose   		// 控制 Bison 解析器的详细输出

// Bison 的声明部分    
%token NAME       	    // 定义终结符或标记的名称
%token NUMBER

%left '+' '-'           // 定义运算符的优先级和结合性
%left '*' '/'

%{
// 在这里可以编写更多的 C 代码
%}// Bison 的规则部分

%%
// 语法规则的定义
expression : expression '+' expression           
            | expression '-' expression           
            | expression '*' expression           
            | expression '/' expression           
            | '(' expression ')'           
            | NUMBER           ;
// 更多的语法规则...
%%

// C 代码部分(选项中的 %{ ... %} 和规则部分中的 %% 之间的部分)
// 在这里可以编写与语法规则相关的 C 代码
int main() {    
    yyparse();  // 调用 Bison 生成的解析函数    
    return 0;
}

bison文件的书写格式与flex文件的书写格式基本一致,只是规则的定义语法不同。

3.3 规则语法介绍

(1)终结符(Terminals)

终结符是语法规则中的基本符号,通常是语言中的关键字、运算符、标识符 等。可以使用**%token**来定义终结符。以下是一个示例:

cpp 复制代码
%token NUMBER 
%token PLUS MINUS TIMES DIVIDE 
%token IDENTIFIER 
%token SEMICOLON

在这个示例中,我们定义了几个终结符,包括数字(NUMBER)、加号(PLUS)、减号(MINUS)、乘号(TIMES)、除号(DIVIDE)、标识符(IDENTIFIER)和分号(SEMICOLON)等。终结符是语法规则中的基本符号,代表语言中的最小单元或词汇元素。终结符在语法分析的过程中与输入字符串的实际内容进行匹配,帮助构建解析树或语法分析树。在 Bison 文件中,终结符通常以大写字母或使用引号括起来的字符串表示。

(2)非终结符(Non-terminals)

非终结符表示语法规则中的抽象结构,可以由其他非终结符和/或终结符组成。您可以使用 %type来定义非终结符的类型。以下是一个示例:

cpp 复制代码
%type <expr> expression

%type <term> term

%type <factor> factor

在这个示例中,我们定义了三个非终结符 expression、term 和 factor,并指定了它们的类型。这些类型标记可以在产生式的操作部分使用,以便对解析树节点进行更复杂的操作。非终结符在语法分析树中代表了一些更高级的结构,可以用来执行语义操作、构建解析树,并帮助描述语言的抽象语法结构。在 Bison 文件中,非终结符通常以小写字母开头。

终结符和非终结符在 Bison 文件中共同定义了语法规则,帮助我们描述和分析特定编程语言或语言的一部分。终结符代表了实际的词法单元,而非终结符则代表了更高层次的语法结构。通过将终结符和非终结符组合起来,我们可以创建复杂的语法规则,用于生成和解析语言的有效字符串。

(3)"文法"

"文法"是一组规则,用于描述编程语言或语言的语法结构。这些规则定义了语言的句法(syntax),即哪些组合是有效的、合法的语句和表达式,以及它们如何组合在一起。文法规则使用产生式(productions)的形式来表示,其中包含终结符(terminals)和非终结符(non-terminals)的组合。

文法规则在 Bison 文件中是使用 BNF(巴科斯-诺尔范式)或 EBNF(扩展巴科斯-诺尔范式)的形式表示的。BNF 是一种形式化的表示方法,用于定义上下文无关文法(Context-Free Grammar),这些文法用于指定编程语言的语法规则。

cpp 复制代码
expression : expression '+' term
          		| expression '-' term
           		| term;

(4) %start

%start 指令用于指定文法的起始非终结符。起始非终结符是语法分析的入口点,也就是从哪个语法规则开始构建解析树或语法分析树。

cpp 复制代码
%start program

%%

statements : statement

                   | statements statement;

statement : assignment

                   | if_statement

                   | while_statement

                   | /* ... other statement types ... */ ;

%start program 指定了起始非终结符为 program。这意味着语法分析将从 program 规则开始,逐步展开其他非终结符,最终构建解析树。在实际语法规则中,起始非终结符的选择取决于您想要分析的语言的语法结构。

(5)$

在语法规则中,$ 用于引用当前产生式的右侧的符号或值。例如,在产生式的右侧,$1 表示该产生式右侧的第一个元素(终结符或非终结符),$2表示第二个元素,依此类推。这些引用用于将产生式右侧的值传递给产生式左侧。注意:生产式的起始下标为1。

(6)$$

在语法规则中,$$ 用于引用当前产生式的结果。当 Bison 解析器完成一个产生式的分析并计算出其结果时,该结果会被赋值给 $$。这通常用于构建解析树的节点或为更高层次的语法规则提供结果。

(7)|

| 用于表示多个产生式之间的选择。它在上下文无关文法中用于定义非终结符的不同产生式形式。每个产生式通过竖线分隔,表示它们是该非终结符的可能形式之一。

3.4 bison文件具体案例

1、创建一个名为parser.l的文件,其中包含词法规则;

cpp 复制代码
%{
#include <stdio.h>
#include <stdlib.h>
%}

//定义终结符
%token SELECT INSERT UPDATE DELETE FROM WHERE 
%token INTO VALUES SET
%token ID INT STRING

%%

//定义规则
	
statement: SELECT columns FROM table WHERE condition ';'
         	| INSERT INTO table '(' columns ')' VALUES '(' values ')' ';'
         	| UPDATE table SET assignments WHERE condition ';'
         	| DELETE FROM table WHERE condition ';'
         	;

columns: ID
       	| columns ',' ID
       	;

table: ID
     	;

assignments: ID '=' value
           	| assignments ',' ID '=' value
           	;

values: value
      	| values ',' value
      	;

value: INT
     	| STRING
     	;

condition: ID '=' value
         	;

%%

int main() {
    	yyparse();
    	return 0;
}

int yyerror(const char *s) {
    	printf("Error: %s\n", s);
    	return 0;
}

2、使用 bison 命令编译 lexer.l 文件

cpp 复制代码
bison -d parser.y

这将生成 parser.tab.c 和 parser.tab.h 两个文件。接下来,你可以将这些文件与你的编译器项目一起编译,并链接到你的代码中。

3.5 抽象语法树(AST)

AST构建步骤:

1、从前缀表达式构建函数关系表里获取当前Token的构建函数,调用该函数构建出一个前缀表达式;

2、查看下一个Token的优先级,如果下一个Token的优先级比当前Token的优先级更高,则说明这可能是一个中缀表达式,或后缀表达式;

3、如果是中缀表达式,则从中缀表达式构建函数关系表里获取下一个Token的构建函数,调用该函数构建出一个中缀表达式;

4、如果是后缀表达式,则从后缀表达式构建函数关系表里获取下一个Token的构建函数,调用该函数构建出一个后缀表达式;

5、通过递归方式,将这些表达式建立起父子关系,最终形成一个抽象语法树。

4.语义分析

在语法分析的基础上,对生成的抽象语法树进行语义分析。语义分析器会检查SQL语句是否符合数据库的语义规则,例如表是否存在、列是否存在、数据类型是否匹配等。如果不符合,则会抛出语义错误。

5.优化器

在语义分析的基础上,进行优化。优化器会对SQL语句进行优化,以提高查询效率。优化器会选择最优的执行计划,包括选择最优的索引、选择最优的连接方式等。

6.执行计划生成器

在优化器的基础上,生成执行计划。执行计划是一组计算机指令,用于执行SQL语句。执行计划包括访问表、过滤数据、排序数据等操作。

7.执行计划执行器

执行计划执行器会按照执行计划执行SQL语句。执行计划执行器会访问表、过滤数据、排序数据等操作,最终返回查询结果。

8.结果集返回

执行计划执行器会将查询结果返回给客户端。查询结果可以是一张表、一组记录或一个标量值。

9.清理

在查询结束后,数据库管理系统会清理执行计划、释放资源等。

未完,writing......

相关推荐
月光水岸New1 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6751 小时前
数据库基础1
数据库
我爱松子鱼1 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser3 小时前
【SQL】多表查询案例
数据库·sql
Galeoto3 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)3 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231113 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
喝醉酒的小白3 小时前
PostgreSQL:更新字段慢
数据库·postgresql
敲敲敲-敲代码3 小时前
【SQL实验】触发器
数据库·笔记·sql