本文主要介绍如何在mysql中添加一条新SQL命令, 例如 DISPATCH ADD "gao", 这条命令会去检查参数的值是否为“gao”, 如果是的话 就把全局变量node_type设为1 (默认为0).
注:关于全局变量node_type 请查看我的另一篇文章<<mysql 添加新启动选项>> : http://hi.baidu.com/gao1738/blog/item/84ff8cde9f8f221f92457ec6.html .
说明: 这里要添加的命令本身没太大意义,主要就是介绍一下mysql的命令解析机制和如何添加命令。
关于mysql如何解析命令可以先阅读下这个链接: http://wenku.baidu.ocom/view/3bb97a1dc5da50e2524d7ff7.html 。
1. 概述
mysql主要是用 lex+yacc 的方式进行命令构建与分析的
我们首先要做的就是在lex和yacc的代码文件中添加新命令的 字符定义与语法定义
然后我们要让mysql认识我们的新命令
最后添加与新命令对应的处理代码
命令解析大体上的流程是:
【1.判断命令类型】->【2.根据类型对命令进行分发,分发到对应解析程序】->【3.对命令解析】->【4.根据具体的命令进行分发,分发到对应的命令处理程序】->【5. 处理命令】
第一步对应的代码在 sql/sql_parse.cc的do_command中。 (目前只碰到过COM_QUERY的类型,还不清楚命令的类型在哪里的决定的,以后补充。)补:所有从客户端发送的sql语句都是COM_QUERY类型的,这个写死在代码里面了,具体的代码在sql-common/client.c中的mysql_send_query函数中。
第二步对应的代码在同一个文件的dispatch_command中。
第三步对应的代码在同文件的sql_parse中。
第四步对应的代码在同文件的mysql_execute_command中。
最后一步对应的代码就不一定了,看具体的命令而定。
2. 在lex和yacc的代码文件中添加新命令的 字符定义与语法定义
修改的文件:
sql/lex.h
sql/sql_yacc.yy
在sql_yacc.yy中添加 新的字符定义 (大约1400行)找到一列的%token定义的地方,在那里添加如下定义:
%token DISPATCH
然后(大约1640行)找到 %type <NONE> 在最后添加上DISPATCH, 如下:
%type <NONE>
'-' '+' '*' '/' '%' '(' ')'sql_yacc.h
',' '!' '{' '}' '&' ' ' AND_SYM OR_SYM OR_OR_SYM BETWEEN_SYM CASE_SYM
THEN_SYM WHEN_SYM DIV_SYM MOD_SYM OR2_SYM AND_AND_SYM DELETE_SYM DISPATCH
在lex.h在中找到 "static SYMBOL symbols[]"在它的末尾(大约624行)添加如下代码:
{ "DISPATCH", SYM(DISPATCH)}
注:
(
在sql/sql_yacc.yy中 通过“%union” 定义了在语法解析过程中 用户输入语句块的可选类型。 例如“int num”表示所有num类型的参数都是整形。 字符型的大都返回的都是LEX_STRING类型, 这个类型在include/m_string.h中定义。
用户可以自己定义类型, 下面是一个例子:
1. 在%union中添加 新类型:
Exec_node *e_node;
2. 定义使用这个类型的语句块:
%type <e_node> name_ip_port
3. 解析语句时使用这个类型:
+dispatch_param:
+ ADD NODE_SYM name_ip_port
+ {
+ LEX *lex= Lex;
+ lex->sql_command= SQLCOM_ADD_DISPATCH_NODES;
+ Lex->execution_node= $3; //这里$3指的是 name_ip_port 这个语句块
+ };
4. 在解析类型对应语句块时创建类型对象:
+name_ip_port:
+ TEXT_STRING_sys TEXT_STRING_sys TEXT_STRING_sys
+ {
+ THD *thd= YYTHD;
+ if (!($$= (Exec_node*)thd->alloc(sizeof(Exec_node)))) //alloc是mysql提供的函数,用于在lex中分配空间
+ MYSQL_YYABORT;
+
+ strcpy($$->name, $1.str);
+ strcpy($$->address, $2.str); //$$表示这个语句块要返回的对象
+ $$->port= atoi($3.str);
+ }
+ ;
)
在sql_yacc.h中找到"enum yytokentype" 在它的末尾添加上dispatch: (这个文件似乎是根据sql/sql_yacc.yy自动生成的,无需自己手动添加)
DISPATCH = 854
和对应的#define DISPATCH 854
(以上忽略)
修改文件:sql/sql_yacc.yy
在sql_yacc.yy中找到 语法定义 “statement:" (大约1723行) (它是语法query的一部分)
在其中添加上 一个语法分支“dispatch”, 例如:
statement:
......
truncate
uninstall
unlock
update
use
xa
dispatch
;
然后在其后添加 dispatch的具体语法:(这里除了定义了DISPATCH ADD 还定义了 DISPATCH DELETE, 但后面的例子只用到DISPATCH ADD)
dispatch:
DISPATCH add_delete;
add_delete:
dispatch_add|dispatch_delete;
dispatch_add:
ADD ident // ident在其他地方定义了, 主要用来表示 字符(例如用于名字的)
{
THD *thd= YYTHD;
LEX *lex= thd->lex;
lex->sql_command= SQLCOM_DISPATCH_ADD; //这里设置了命令标识, 这个标识的定义在下一步中会介绍
char *tmp=(char*) thd->alloc(sizeof(char)); //这里需要为变量字符在lex的存储空间中分配一个空间来存变量值
strcpy(tmp, $2.str); // $2表示命令的第2个部分,这里是指"ADD indent"中的indent
lex->dispatch_message=tmp; //lex->dispatch_message是在lex结构中新定义的,后面会介绍
};
dispatch_delete:
DELETE_SYM ident
{
THD *thd= YYTHD;
LEX *lex= thd->lex;
lex->sql_command= SQLCOM_DISPATCH_DELETE;
lex->dispatch_message= $2.str;
}
;
注: 这里需要注意避免定义的语法有2义性, 这会导致一个编译错误:“sql/sql_yacc.yy:expected 164 shift/reduce conflicts”
3. 让mysql认识我们的新命令
修改文件:sql/sql_cmd.h
sql/mysqld.cc
sql_cmd.h找到"enum enum_sql_command"(大约88行)中添加如下代码:
SQLCOM_DISPATCH_ADD, SQLCOM_DISPATCH_DELETE
这就是2个新命令的标识。它们在上一步的语法定义中被使用。
然后在mysqld.cc中大约3093行找到 "SHOW_VAR com_status_vars[]"在它的最后一项前添加如下代码:
{"dispatch_add", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DISPATCH_ADD]),SHOW_LONG_STATUS},
{"dispatch_delete", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DISPATCH_DELETE]),SHOW_LONG_STATUS},
{NullS, NullS, SHOW_LONG}
};
注:以上在mysqld.cc中的修改主要是为了能通过mysql非常严格的编译脚本,否则无法通过mysqld.cc中大约3259行的断言compile_time_assert,关于其中的意义现在还不是很了解,以后补充。
3.添加与新命令对应的处理代码
修改文件: sql/sql_lex.h
在sql_lex.h中找到“struct LEX”的定义(大约2054行)然后在其中添加如下代码:
char* x509_subject,*x509_issuer,*ssl_cipher;
String *wild;
char *dispatch_message;
sql_exchange *exchange;
select_result *result;
这里定义的“dispatch_message” 就是用来存储命令的参数值的。该变量在第一步中的语法定义中被使用。
修改文件:sql/sql_parse.cc, 可能还有其他的,看具体需求
在sql_parse.cc中找到 “mysql_execute_command” 函数, 在其中有个很大的swith. 在这个swith中添加我们新命令对应的case:
case SQLCOM_DISPATCH_ADD:
case SQLCOM_DISPATCH_DELETE:
{
if (!strcmp(lex->dispatch_message, "gao"))
{
global_system_variables.node_type=1;
my_ok(thd); //这句代码设置查询的返回状态为ok 这很重要,否则无法通过mysql在
//查询对应的状态由thd->stmt_da->status()查看
//该值将会在sql/Protocol的end_statement中进行判断处理,如果为空的话
//将导致DBUG_ASSERT(0);
//注:这里如果要添加验证并做对应的错误处理,可以使用如下语句:
// my_error(ER_WRONG_ARGUMENTS, MYF(0), "something you want to say in the error");
//“ER_WRONG_ARGUMENTS” 这个是错误类型, 在include/mysqld_error.h中定义的。 这个错误类型可以在测试用例中用--error捕捉,例如:
//--error ER_WRONG_ARGUMENTS
//DISPATCH ADD node_test;
//如果测试用例在执行“DISPATCH ADD node_test;” 时产生“ER_WRONG_ARGUMENTS ” 错误的话, 这个错误会被捕捉,并将预定义的
//错误信息打印。 使用这种方式可以在测试用例中添加错误测试,并设置预期要捕捉的错误
//“MYF(0)” 是把0转成int类型, 这个宏在include/my_global.h中定义, 这个值如果设置成1 的话,mysql会向标准错误输出中输入/007, 即
//一个错误提示音。 相关的代码处理在mysys/my_mess.c的24行,函数my_message_stderr中。
}
else
my_eof(thd);
break;
}
4. 对应的测试用例
在mysql-test/t 下面建立一个测试用例文件gao.test, 内容如下:
DISPATCH ADD gao;
show variables like 'node_type';
然后在mysql-test/目录下执行
./mtr gao
结果如下:
==============================================================================
TEST RESULT TIME (ms) or COMMENT
--------------------------------------------------------------------------
worker[1] Using MTR_BUILD_THREAD 300, with reserved ports 13000..13009
DISPATCH ADD gao;
show variables like 'node_type';
Variable_name Value
node_type 1
main.gao [ pass ] 3