本想续上节,介绍Leveldb引擎的插入操作,但涉及许多Parser相关的内容。
就先分析Parser,前面章节提到SQL解析,只是简单介绍一下,这里将介绍核心的类对象和流程。
sql_yacc.cc与sql_yacc.h文件的产生,如下所示:
/usr/bin/bison --name-prefix=MYSQL --yacc --output=./sql_yacc.cc
--defines=./sql_yacc.h ./sql_yacc.yy
关键点就是前缀,通过宏将内部函数与外部声明函数建立关联
#define yyparse MYSQLparse
#define yylex MYSQLlex
#define yyerror MYSQLerror
#define yydebug MYSQLdebug
#define yynerrs MYSQLnerrs
extern int MYSQLparse(class THD *thd);
// 实现sql_parser.cc中函数声明
sql_yacc.yy文件中如下两行表示yyparse和yylex函数的参数。
%parse-param { class THD *YYTHD }
%lex-param { class THD *YYTHD }
class Parse_tree_node{};
/**
parse_tree_node_base.h
解析树节点的基类,虚类
单纯记录节点是否语境化
Item类即是其友元类又是其派生类,Item通过友元直接访问其内部变量
并且Item类将其成员函数私有化,Item类通过itemize函数实现语境化
就设计而言个人感觉很怪[^_^],派生类是基类的友元
*/
class Item : public Parse_tree_node{};
/**
item.h
Item类是解析树的基本元素,抽象类
Expression对象的基类(https://dev.mysql.com/doc/refman/5.7/en/expressions.html)
功能很复杂,感觉其包含许多子类的功能,这样好处是直接可调用基类中的函数
若将功能推迟到派生类,处理时需要将其转换为派生类才能调用函数
定义许多bool值成员,通过bool值表示expression的某些特性
*/
class Table_ident :public Sql_alloc{};
/* sql_class.h
解析时SQL语句中的表对象
add_table_to_list()函数利用Table_ident构建TABLE_LIST对象,
并将其插入到lex->query_tables全局链表中
*/
class PT_statement : public Parse_tree_node{};
/**
parse_tree_nodes.h
解析时SQL语句对象的基类,抽象类
只有一个纯虚函数成员,make_cmd
返回一个运行时使用的Sql_cmd对象,这样parser与runtime就关联起来
*/
class Sql_cmd : public Sql_alloc{};
/**
sql_cmd.h
执行时SQL语句对象的基类,抽象类
execute纯虚函数成员,实现语句执行操作
由具体的派生类实现
*/
class Query_tables_list{};
/**
sql_lex.h
SQL语句中使用到的表列表,以及open和lock相关信息
*/
struct LEX: public Query_tables_list{};
/**
sql_lex.h
LEX分析的状态以及结果信息,包含解析后的所有信息
*/
class st_select_lex: public Sql_alloc{};
/**
sql_lex.h
SQL语句中的查询块,或规范的查询
*/
class st_select_lex_unit: public Sql_alloc{};
/**
sql_lex.h
查询表达式,由一个或多个st_select_lex构成
*/
struct TABLE_LIST{};
/**
table.h
from子句中的所有引用的表构成的LIST对象
*/
struct TABLE{};
/**
table.h
具体的表对象,与底层存储引擎交互
调用open_tables_for_query函数实例化
*/
struct TABLE_SHARE{};
/**
table.h
TABLE对象共享的实例,每个表只存在一个实例,表元数据信息
*/
CREATE TABLE parse_t(
id INT NOT NULL,
cv1 VARCHAR(20) DEFAULT "",
tt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
dayweek SMALLINT,
cv2 INT,
PRIMARY KEY(id)
) ENGINE=innodb DEFAULT CHARSET=utf8mb4;
解析核心是将SQL语句转换PT_statement类对象。
CREATE TABLE比较简单,未定义其对应的PT_statement类。
例子中核心两点是:
//解析过程即实现语境的操作中,其中关键流程如下:
lex->sql_command= SQLCOM_CREATE_TABLE;
/**
sql_yacc.yy:2262
确定command类型
*/
st_select_lex::add_table_to_list();
/**
sql_yacc.yy:2263
1、以Table_ident对象构造TABLE_LIST
2、将新对象插入到thd->lex->select_lex->table_list链表中
*/
//sql_yacc.yy:6259 键匹配,构建Key实例将其存入lex->alter_info.key_list链表中
/**
sql_yacc.yy:6467 字段数据类型匹配,
sql_yacc.yy:6880 字段属性匹配
*/
add_field_to_list();
/**
sql_yacc.yy:6344
1、检查字段名称长度是否合法
2、确定字段是否是KEY,若是构建KEY,存入lex->alter_info.key_list
本例中主键单独行定义,走sql_yacc.yy:6259匹配
3、检查字段类型与默认值以及ON UPDATE是否匹配
4、构建Create_field,存入lex->alter_info.create_list
*/
class Key_part_spec :public Sql_alloc{}; //sql_class.h
//本例中未用到,由多字段组成的键中的每个字段都是Key_part_spec
class Key{}; //sql_class.h
//表中定义的键,
class Create_field :public Sql_alloc{}; //field.h
//表中字段对象,包含字段所有信息
class Alter_info{};
/**
sql_alter.h
描述表信息,CREATE TABLE与ALTER TABLE共用
lex->alter_info即为Alter_info实例
*/
MySQL支持三类Insert语法:
分析例句如下:
INSERT INTO parse_t(id, cv1, tt, dayweek, cv2)
values (DAY(NOW()), 'simple e xample', NOW(), DAYOFWEEK(NOW()), 12),
(2, 'simple example2', NOW(), HOUR(NOW()), (20 - 14));
“`C++
/**
sql_yacc.yy:11344
insert_stmt三种匹配模式对应上述三类语法。
核心是匹配中insert_from_constructor
*/
class PT_insert_values_list : public Parse_tree_node{};
/**
VALUES中行解析为双重链表,存入PT_insert_values_list中many_values字段中
*/
class PT_insert : public PT_statement{};
/**
sql_yacc.yy:11354
语句解析完成,实例化PT_insert对象
*/
/**
sql_yacc.yy:12294
调用PT_insert::make_cmd函数,实例化Sql_cmd_insert对象
*/
//PT_insert::make_cmd中语境处理非常关键
PT_insert::contextualize();
//语境化处理将Parser与运行时对象关联起来
//主要功能:
// 1、为lex->sql_command赋值表示CMD类型
lex->sql_command = SQLCOM_INSERT;
//2、构造TABLE_LIST对象,将其插入lex->select_lex->table_list链表中
pc->select->add_table_to_list(pc->thd, table_ident, NULL,
TL_OPTION_UPDATING,
yyps->m_lock_type,
yyps->m_mdl_type,
NULL,
opt_use_partition);
//3、设置表的锁类型,open_tables()时用到
pc->select->set_lock_for_tables(lock_option);
//4、column_list语境化
column_list->contextualize(pc);
/**
5、row_value_list语境化
着重分析DAYOFWEEK(NOW())的解析与语境
*/
row_value_list->contextualize(pc)
/**
由于weekofday函数名并不是SQL中标记单词,匹配function_call_generic sql_yacc.yy:9986
构造PTI_function_call_generic_ident_sys实例 sql_yacc.yy:9989
其参数NOW(),匹配sql_yacc.yy:10045,构造PTI_udf_expr实例
PTI_function_call_generic_ident_sys语境化:
1、检查名称字符是否合法,长度
sp_check_name(&ident);
2、查询其是否是内置函数,若存在返回其建造者
find_native_function_builder(thd, ident);
3、建造者构建Item_func_weekday对象
*res= builder->create_func(thd, ident, opt_udf_expr_list);
4、Item_func_weekday语境化处理,调用Item_func::itemize函数
主要是对参数进行语境化处理
*/
“
语境化中Item::fixed成员被处理,若值需进一步处理则为0,表示未处理。若是数字或字符串则为1。
fill_record()函数中才处理得到真实值。
至此,Parser部分分析结束,介绍其涉及的类型以及其中关键的的步骤。
核心就两个点,yacc匹配处理和语境化处理。
parser部分设计挺优雅,理清关系流程后,分析定位都比较简单。