Where pegs grow legs: hanging ideas on words

“I have no special talents. I am only passionately curious.” ~ Albert Einstein

Your first MySQL source code patch

At the MySQL Hackfest Camp of the MySQL conference, Mark Callaghan, MySQL code extraordinaire, helped us to hack our first change to the MySQL source code. In an hour, we implemented an additional command “SHOW HELLO” to the MySQL server. In and of itself, the command is not very useful per se, but I wanted to share what Mark showed us to modify MySQL’s SQL parsing code. Getting started with creating your own patch isn’t as hard as you think!

It was nice having a MySQL code expert by your side, telling you what files you need to look into to make your changes work. Here is how he showed us how to add a “SHOW HELLO” to the existing SHOW commands in the MySQL server. Once you’re done, it works like the following:

mysql> SHOW HELLO;
+--------------------+
| Hello Output       |
+--------------------+
| Good day to you!   |
+--------------------+
1 row in set (0.00 sec)

mysql>

Files we’ll be modifying

There are a few files we’ll have to open. Luckily, all of them are in the sql/ directory. This makes sense since we’re modifying the parser. Here’s the full list at a glance.

  • sql/sql_yacc.yy – modifications in 4 places – bison then converts this file into .h and .cc files
  • sql/lex.h – modifications in 1 place – reserve HELLO as a symbol
  • sql/sql_lex.h – modifications in 1 place – let server know about new command
  • sql/sql_parse.cc – modifications in 1 place – maps parsing to execution code
  • sql/mysql_priv.h – modifications in 1 place – declare function that does execution in sql_show.cc
  • sql/sql_show.cc – implement the actual function that does the work

The modifications

sql_yacc.yy

In my_yyoverflow(), around line 321. Add a token HELLO_SYM.

%token  HAVING
%token  HELP_SYM
%token  HELLO_SYM
%token  HEX_NUM
%token  HIGH_PRIORITY

Around line 6913, tell the lexer to look for another command.

| MUTEX_SYM STATUS_SYM
  { Lex->sql_command = SQLCOM_SHOW_MUTEX_STATUS; }
| opt_full PROCESSLIST_SYM
  { Lex->sql_command= SQLCOM_SHOW_PROCESSLIST;}
| HELLO_SYM
  { Lex->sql_command= SQLCOM_SHOW_HELLO;}
| opt_var_type  VARIABLES wild_and_where
  {
    LEX *lex= Lex;
    lex->sql_command= SQLCOM_SELECT;
    lex->orig_sql_command= SQLCOM_SHOW_VARIABLES;
    lex->option_type= $1;
    if (prepare_schema_table(YYTHD, lex, 0, SCH_VARIABLES))
      YYABORT;
  }

Around 8047, add a keyword.

| GLOBAL_SYM            {}
| HASH_SYM              {}
| HELLO_SYM             {}
| HOSTS_SYM             {}
| HOUR_SYM              {}

lex.h

In the symbols[] array, around line 226.

{ "HASH",             SYM(HASH_SYM)},
{ "HAVING",           SYM(HAVING)},
{ "HELLO",            SYM(HELLO_SYM)},
{ "HELP",             SYM(HELP_SYM)},
{ "HIGH_PRIORITY",    SYM(HIGH_PRIORITY)},

sql_lex.h

In enum_sql_command(), add a command.

SQLCOM_SHOW_INNODB_STATUS, SQLCOM_SHOW_NDBCLUSTER_STATUS, SQLCOM_SHOW_MUTEX_STATUS,
SQLCOM_SHOW_PROCESSLIST, SQLCOM_SHOW_MASTER_STAT, SQLCOM_SHOW_SLAVE_STAT, SQLCOM_SHOW_HELLO,
SQLCOM_SHOW_GRANTS, SQLCOM_SHOW_CREATE, SQLCOM_SHOW_CHARSETS,

sql_parse.cc

Somewhere inside mysql_execute_command(), add the following. I’ve added around line 3725.

case SQLCOM_SHOW_PROCESSLIST:
  if (!thd->security_ctx->priv_user[0] &&
    check_global_access(thd,PROCESS_ACL))
  break;
  mysqld_list_processes(thd,
                       (thd->security_ctx->master_access & PROCESS_ACL ?
                        NullS :
                        thd->security_ctx->priv_user),
                       lex->verbose);
  break;
case SQLCOM_SHOW_HELLO:
  mysqld_print_hello(thd);
  break;
case SQLCOM_SHOW_STORAGE_ENGINES:
  res= mysqld_show_storage_engines(thd);
  break;

mysql_priv.h

Around line 909, under sql_show.cc comments.

/* sql_show.cc */
bool mysqld_show_open_tables(THD *thd,const char *wild);
bool mysqld_show_logs(THD *thd);
void mysqld_print_hello(THD *thd);
void append_identifier(THD *thd, String *packet, const char *name,
uint length);
int get_quote_char_for_identifier(THD *thd, const char *name, uint length);

sql_show.cc

Around line 1267, implement the new command that will run when you call SHOW HELLO.

#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION
template class I_List ;
#endif

void mysqld_print_hello(THD *thd)
{
  sql_print_error("enter_hello");
  Item *field;
  List field_list;
  field_list.push_back(new Item_empty_string("Hello Output",16));
  Protocol *protocol= thd->protocol;
  if (protocol->send_fields(&field_list,
  Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
  {
    sql_print_error("can't send_fields - hello");
    return;
  }
  protocol->prepare_for_resend();
  protocol->store("Good day to you!", system_charset_info);
  if (protocol->write())
  {
    sql_print_error("cant write - hello");
    return; /* purecov: inspected */
  }
  send_eof(thd);
  sql_print_error("exit_hello");
  return;
}

void mysqld_list_processes(THD *thd,const char *user, bool verbose)
{
  Item *field;
  List field_list;

Testing

Once you make the changes, compile MySQL using make, and test it. You should be doing this for every change that you intend to give back to MYSQL. To test, you need to create a .test and .result file inside the mysql-test/t and mysql-test/r directories respectively. You can name the files whatever you want, just make sure to call them the same thing.

mysql-test/t/hello_file.test

# Test for show hello
show hello;

mysql-test/r/hello_file.result

show hello;
Hello Output
Good day to you!

Once you’ve added those files, go into the mysql-test directory, and run ./mtr hello_file. This runs the new test you just added. If it passes, it works, and if not, you need to go back and look at something.

Summary

That’s it! That wasn’t too hard, was it? I realize that you probably would have had a much harder time finding all these places on your own. However, once someone shows you what pieces are important, you should be able to make other changes easily. Hopefully, there are others very familiar with the MySQL code base that would be willing to write a post about how to write a UDF, or add a new SQL function. Plus, I’d love to hear how best to send MySQL row change log output to something like Hypertable or Hbase!

No comments Digg this

No comments yet. Be the first.

Leave a reply