写在前面:
入职之后首次工作的内容为给某自研数据库添加一条与ALTER
相关的SQL解析,并调用相应功能。 在此敏感信息已做脱敏处理,不知道影不影响阅读。
向MySQL添加简单SQL语句——以ALTER为例 本文仅分享一些实现ALTER SQL语句的一些微小的经验与踩的坑,能力一般水平有限,文章面向小白,用词简单粗暴,存在的错误欢迎大佬们批评指正 ; )
设计SQL 在开始动手修改词法解析过程之前,需要对要实现的SQL有哪些变体做到心里有数,pingcap的SQL图 就是一个很好地展示SQL各种变体的方式,在此我来做一个拙劣的模仿(图片已脱敏,对应关键词用KEY n代替):
由于后续词法解析的设计需要一些技巧来尽可能的共享解析的函数,因此在开始着手写代码时要对需要实现SQL的所有语法上的可能性心里有数,设计完成后就可以开始着手改代码了。
词法解析 mysql-server词法解析的方式都写在sql_yacc.yy
中,词法解析就是将SQL语句解析为AST树的过程,并且整个过程可以看作一系列函数的递归调用。
添加SQL语句在词法解析部分可以分为以下几个步骤:
添加非保留关键字 需要添加的SQL语句中了涉及到MySQL中没有的关键字KEY 0
、KEY 1
、KEY 2
、KEY 3
等,因此在实现SQL语句之前需要先添加其所需的关键字。
关键字分为保留关键字和非保留关键字,保留关键字不允许将其作为库/表/列/别名,这可能会引发兼容性问题,因此本次添加的关键字需要保证其是作为非保留关键字。sql_yacc.yy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 %token<lexer.keyword> KEY_0_SYM 1366 %token<lexer.keyword> KEY_1_SYM 1367 %token<lexer.keyword> KEY_2_SYM 1368 %token<lexer.keyword> KEY_3_SYM 1369 ... /* These are the non-reserved keywords which may be used for unquoted identifiers everywhere without introducing grammar conflicts: */ ident_keywords_unambiguous: ACTION | ACCOUNT_SYM | ACTIVE_SYM ... | KEY_0_SYM | KEY_1_SYM | KEY_2_SYM | KEY_3_SYM ;
在添加token时需要添加<lexer.keyword>
,否则该关键字会被列入information_schema_keywords中,并在ident_keywords_unambiguous下添加本次的关键字,以保证其为非保留关键字。
如果需要关键字没有成功地被添加为非保留关键字,可能会导致各种或大或小的兼容性问题,如:
在lex.h
中添加token对应的字符串:
1 2 3 4 5 6 7 8 9 10 11 static const SYMBOL symbols[] = { {SYM ("&&" , AND_AND_SYM)}, {SYM ("<" , LT)}, {SYM ("<=" , LE)}, ... {SYM ("KEY_0" , KEY_0_SYM)}, {SYM ("KEY_1" , KEY_1_SYM)}, {SYM ("KEY_2" , KEY_2_SYM)}, {SYM ("KEY_3" , KEY_3_SYM)}, ... }
添加词法解析过程 现在词法解析器可以解析我们添加的非保留关键字了,接下来就需要说明词法解析的过程了。
MySQL中本身是有ALTER INSTANCE语法的,我们需要先找到MySQL中原本的ALTER INSTANCE的解析逻辑。一般而言,一句SQL语句解析过程的开始部分都以_stmt作为结尾:
parse_yacc.yy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 %type <top_level_node> alter_instance_stmt alter_resource_group_stmt ... ... %type <alter_instance_cmd> alter_instance_action ... alter_instance_stmt: ALTER INSTANCE_SYM alter_instance_action { Lex->sql_command= SQLCOM_ALTER_INSTANCE; $$= $3; } ;
可以看到,ALTER INSTANCE_SYM为语句的匹配的开头,接下来就是对应的具体操作alter_instance_action
。其中alter_instance_stmt
的返回值为<top_level_node>
,而alter_instance_action
的返回值为<alter_instance_cmd>
。其中$$
可以理解为当前代码块中的“返回值”,而$n
指的是对应位置声明的返回值。
%type
的声明可以看做.yy与.cc的桥梁,而<top_level_node>
与<alter_instance_cmd>
的定义则在parser_yytype.h中:
parser_yytype.h
1 2 3 4 5 ... Parse_tree_root *top_level_node; ... PT_alter_instance *alter_instance_cmd; ...
由于原本的alter_instance逻辑已经很乱了,且尽可能减少与MySQL本身的代码产生冲突,我们另起炉灶,建立属于MY-DB的操作:my_alter_instance_action
,至于my_alter_instance_action
的定义将在后续的章节中说明:
parse_yacc.yy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 %type <my_alter_instance_action> my_alter_instance_action my_instance_operation_body ... alter_instance_stmt: ALTER INSTANCE_SYM my_alter_instance_action { Lex->sql_command= SQLCOM_ALTER_INSTANCE; $$= $3; } | ALTER INSTANCE_SYM alter_instance_action { Lex->sql_command= SQLCOM_ALTER_INSTANCE; $$= $3; } ; ... my_alter_instance_action: KEY_0_SYM my_instance_operation_body opt_key_4 { $$ = $2; $$->with_opt($3); } ; ... opt_key_4: /* empty */ { $$= false; } | KEY_4_SYM { $$= true; } ;
在mysql-server的语法解析中,如KEY_4
等可选的匹配会以opt_
作为前缀,以空的匹配作为开头,并指定其默认的返回值。
以最简单的KEY_0
操作为例:
parse_yacc.yy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 %type <mystruct_identify> mystruct_units_0 mystruct_units_1 ... my_instance_operation_body: mystruct_units_1 KEY_2_SYM mystruct_units_0 { $$ = NEW_PTN PT_my_alter_instance(my_alter_instance_action_enum::OPERATE_0, $1, $3); if ($$ == nullptr) { MYSQL_YYABORT; } } ; ... mystruct_units_0: key_1_or_alias id_list { $$ = NEW_PTN My_identify(Identify_type::TYPE_0, $2, nullptr); if ($$ == nullptr) { MYSQL_YYABORT; } } ; mystruct_units_1: opt_key_3_sym string_list { $$ = NEW_PTN My_identify(Identify_type::TYPE_1, nullptr, $2); if ($$ == nullptr) { MYSQL_YYABORT; } } ; key_1_or_alias: KEY_1_SYM {} | KEY_1_ALIAS_SYM {} // 这个没有在关键字定义处写明,懒得加了,仅供理解 ; ) ; opt_key_3_sym: /* empty */ {} | KEY_3_SYM {} ;
AST树的执行过程 Parse_tree 在mysql-server中的AST树有两种常用类:Parse_tree_root
、Parse_tree_node
,为了实现方便,这里没有使用node:
parse_tree_nodes.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class PT_my_alter_instance final : public Parse_tree_root { Sql_my_cmd_alter_instance sql_cmd; public : explicit PT_my_alter_instance ( my_alter_instance_action_enum alter_instance_action_arg, My_identify *src, My_identify *dst) : sql_cmd(alter_instance_action_arg, src, dst) { } Sql_cmd *make_cmd (THD *thd) override ; inline void with_opt (bool opt) { sql_cmd.with_opt = opt; } inline void set_args (My_alter_pair_list *args) { sql_cmd.args = args; } inline void set_key (String *key) { sql_cmd.key = key; } inline void set_xxx (XXX *xxx) { sql_cmd.xxx = xxx; } };
其中Sql_cmd *make_cmd(THD *thd)
是基类Parse_tree_root
的虚函数,用于返回内部类Sql_mc_cmd_alter_instance
,这个会在以下过程中被调用,生成Sql_cmd
:
1 2 3 4 sql_yacc.cc::MAKE_CMD () sql_class.cc::THD::sql_parser () |-> sql_lex.cc::LEX::make_sql_cmd (Parse_tree_root *parse_tree) |-> parse_tree_nodes.cc::make_cmd (THD *thd)
由于ALTER INSTANCE的过程不需要prepare,因此只需要实现两个虚函数:execute
和sql_command_code
,其中重要的是execute
函数,它在sql_parse.cc::mysql_execute_command()
处被调用,大多数的SQL语句都在这里执行。
Sql_cmd Sql_cmd类与这里用到的继承它的子类Sql_mc_cmd_alter_instance
定义如下:
sql_cmd.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Sql_cmd { private : Sql_cmd (const Sql_cmd &); void operator =(Sql_cmd &); public : virtual enum_sql_command sql_command_code () const = 0 ; ... virtual bool execute (THD *thd) = 0 ; ... }
sql_admin.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Sql_my_cmd_alter_instance : public Sql_cmd { friend class PT_my_alter_instance ; const enum my_alter_instance_action_enum alter_instance_action; My_identify *src; My_identify *dst; bool with_opt; union { My_alter_pair_list *args; String *key; XXX *xxx; void *ptr; }; Alter_instance *alter_instance; public : explicit Sql_my_cmd_alter_instance ( my_alter_instance_action_enum alter_instance_action_arg, My_identify *src, My_identify *dst) : alter_instance_action(alter_instance_action_arg), src(src), dst(dst), ptr(nullptr), alter_instance(nullptr) { } bool execute (THD *thd) override ; enum_sql_command sql_command_code () const override { return SQLCOM_ALTER_INSTANCE; } };
其中sql_command_code()
负责返回cmd的类型,而execute()
负责SQL的具体执行流程,Sql_my_cmd_alter_instance::execute()
定义如下:
sql_admin.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 bool Sql_my_cmd_alter_instance::execute (THD *thd) { bool res = true ; switch (alter_instance_action) { case OPERATE_0: { alter_instance = new My_operation_0 (thd, src, dst, with_opt); } break ; case OPERATE_1: { alter_instance = new My_operation_1 (thd, src, dst, args); } break ; case OPERATE_2: { alter_instance = new My_operation_2 (thd, src, dst, ptr, with_opt); } break ; case OPERATE_3: { alter_instance = new My_operation_3 (thd, src, dst); } break ; default : { my_error (ER_NOT_SUPPORTED_YET, MYF (0 ), "ALTER INSTANCE" ); } return true ; } if (!alter_instance) { my_error (ER_OUT_OF_RESOURCES, MYF (0 )); } else { res = alter_instance->execute (); delete alter_instance; alter_instance = nullptr ; } return res; }
在这个函数中会根据alter_instance_action
的类型创建不同的Alter_instance
类,并调用其execute()
函数。
Alter_instance Sql_my_cmd_alter_instance::execute()
在本次调度函数中的Alter_instance
与其中间子类My_alter_instance
实现如下:
sql_alter_instance.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Alter_instance { protected : THD *m_thd; public : explicit Alter_instance (THD *thd) : m_thd(thd) { } virtual bool execute () = 0 ; bool log_to_binlog () ; virtual ~Alter_instance () = default ; }; ... class My_alter_instance : public Alter_instance { public : explicit My_alter_instance (THD *thd, My_identify *src_ident, My_identify *dst_ident) : Alter_instance(thd), src(src_ident), dst(dst_ident) { } ~My_alter_instance () override = default ; inline bool check_access () ; bool check_my_identify_valid () { return check_src_valid () && check_dst_valid () && check_additional (); } protected : My_alter_instance *src; My_alter_instance *dst; virtual bool check_src_valid () = 0 ; virtual bool check_dst_valid () = 0 ; virtual bool check_additional () = 0 ; };
My_operation_0、1、2、3
分别继承My_alter_instance
,并且override Alter_instance::execute()
。
返回执行结果 对于ALTER INSTANCE而言,只需要返回error或者ok即可,对应了mysql的两个函数mysql_error()
与mysql_ok()
:
1 2 3 4 5 void my_error (int nr, myf MyFlags, ...) ;void my_ok (THD *thd, ulonglong affected_rows, ulonglong id, const char *message) ;
my_error() my_error()
的第一个参数为报错信息的宏,可以在messages_to_clients.txt
中定义,如本次使用的ER_WRONG_PARAM_NUM
等:
1 2 3 4 5 6 7 8 9 10 11 12 13 ... ER_WRONG_PARAM_NUM eng "Incorrect number of parameter '%s', need '%u' but get '%u'" ER_LESS_PARAM_NUM eng "Incorrect number of parameter '%s', need more than '%u' but get '%u'" ER_UNKNOWN_ARG eng "Unknown arg: '%s'" ER_SUBMIT_JOB_FAILED eng "Submit job failed: '%s', err_msg: '%s'" ...
一个使用的例子:
1 my_error (ER_SUBMIT_JOB_FAILED, MYF (0 ), "TRANSFER LEADER" , job_status.second.c_str ());
my_ok() my_ok()
的四个参数分别为:
一个使用的例子:
1 2 std::string ret = "job_id: " + std::to_string (job_status.first); my_ok (m_thd, 0 , 0 , ret.c_str ());