GA

2014/12/03

過去、現在、未来、全宇宙に存在する全てのクソクエリーを、生まれる前に自分の手でカジュアルに消し去ること(仮)

この記事は MySQL Casual Advent Calendar 2014 の3日目の記事です。

クソクエリーについての名言 がつい先週生まれたばかりですが、みなさま如何お過ごしでしょうか。そういえば今年は Kuso-query As A Code みたいな話もさせてもらいました。

過去、現在、未来、全宇宙に存在する全てのクソクエリーを、生まれる前に自分の手でカジュアルに消し去るため(仮)に、MySQL 5.7.5-labsのQuery Rewrite Plugin の記事では触れるだけだったオレオレrewriteプラグインを書いてみました。君のクエリにレボ☆リューション! (仮)

どういう動作をさせるかというと、


mysql57> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql57> SELECT (1);
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql57> SELECT ((1));
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql57> SELECT (((1)));
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Your query is f**king!!' at line 1

mysql57> SELECT ((1)), ((2));
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

mysql57> SELECT (((1)), ((2)));
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Your query is f**king!!' at line 1

mysql57> SHOW WARNINGS;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                   |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'SELECT (((1)), ((2)))' rewritten to 'Your query is f**king!!' by plugin: rewrite_test.                                                                             |
| Error | 1064 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Your query is f**king!!' at line 1 |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

シンプルな話、クソっぽいクエリー(今実装してあるのは、かっこが3つ以上ネストしているもの)が来た時に強制的にクエリーを上書きします。そしてこの文字列はSQLとして成立していないので、 *必ず* シンタックスエラーになります。
パーサーのレイヤーで書き換えているので、スローログとかもこの通り。


$ tail slow.log
# User@Host: root[root] @ localhost []  Id:     2
# Query_time: 0.000212  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
use d1;
SET timestamp=1417408435;
SET SESSION long_query_time= 0;
# Time: 2014-12-01T04:33:56.545809Z
# User@Host: root[root] @ localhost []  Id:     2
# Query_time: 0.000088  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1417408436;
Your query is f**king!!;

これでもう2度と、派生テーブル同士を相関サブクエリーで結合したものをUNION DISTINCTで結合するとかいうクソクエリーに二度とお目にかかることはありません。もうちょっと真面目に書けば、テーブルを4つ以上JOINしてるとか、CREATE TABLEの中のflgxなんてカラム名に反応させてリライトさせることも可能です。

さて、ではこんなオレオレぷらぎんの作り方を解説します。こっちが本題だよ。
ビルドはt2.smallにamzn-ami-hvm-2014.09.1.x86_64-ebs (ami-b66ed3de)のAMIでやっています。t2.microだとメモリー食いきられてmakeできなかった。。URLは2014/12/01現在のものです。

まず、何はなくともlabs.mysql.comから"MySQL Optimizer/InnoDB/Replication" のソースコードを落としてこないとなんだけど、なんかどうも2ヶ月くらい前からファイルの終盤で"Connection reset by peer"を食らうようになっちゃってます。なんだろうこれ。wgetががんばってリトライしてくれるので、それほど困ってませんが。


$ wget http://downloads.mysql.com/snapshots/pb/mysql-5.7.5-labs-preview/mysql-5.7.5-labs-preview.tar.gz
$ tar xzf mysql-5.7.5-labs-preview.tar.gz
$ cd mysql-5.7.5-labs-preview
$ sudo yum install -y cmake gcc gcc-c++ ncurses-devel

Write Yourself a Query Rewrite Plugin: Part 1 | MySQL Server Blog から読み取るに、プラグインそのもののソースと"plug.in"ファイル、CMakeLists.txtを放り込んでやればいいらしい。


$ cd plugin/
$ ll rewrite_example
total 12
-rw-r--r-- 1 ec2-user ec2-user  835 Sep 24 05:06 CMakeLists.txt
-rw-r--r-- 1 ec2-user ec2-user  278 Sep 24 05:06 plug.in
-rw-r--r-- 1 ec2-user ec2-user 2697 Sep 24 05:06 rewrite_example.cc

$ cp -r rewrite_example mysql_casual

$ cd mysql_casual/

というわけで公式のrewrite_exampleをスケルトンにして実装していく。まずはCMakeLists.txtでこれは難しくない。i_sぷらぎんとかと同じで元になるソースの名前とモジュールの名前を決めてやるだけ。


$ vim CMakeLists.txt
..
MYSQL_ADD_PLUGIN(do_not_allow_kuso_query mysql_casual.cc
  MODULE_ONLY MODULE_OUTPUT_NAME "mysql_casual")

"plug.in"も同じ感じだった。ダイナミックリンクするときのファイル名と、スタティックリンクする時のファイル名を指定する? (i_sぷらぎんの時には書かなかったなこれ)


$ vim plug.in
MYSQL_PLUGIN(do_not_allow_kuso_query, [Be extinct all of kuso-query, before those are born.]),
                                      [Example query rewrite plugin by yoku0825.]
MYSQL_PLUGIN_DYNAMIC(do_not_allow_kuso_query, [mysql_casual.la])
MYSQL_PLUGIN_STATIC(do_not_allow_kuso_query, [mysql_casual.a])

で、核になるリライトぷらぎん本体。Query rewrite用にAPIが切ってあるので、それを呼ぶ感じで実装していく。やっぱりrewrite_example.ccをリネームしてそこを直してみるのが早い。


$ mv -i rewrite_example.cc mysql_casual.cc
$ vim mysql_casual.cc
..
static st_mysql_rewrite_pre_parse rewrite_example_descriptor= {
  MYSQL_REWRITE_PRE_PARSE_INTERFACE_VERSION,    /* interface version          */
  your_query_is_fxxking_dude,                   /* rewrite raw query function */
  free_rewritten_query,                         /* free allocated query       */
};..

先頭からQuery Rewrite用APIのバージョン, クエリーのリライトに使う実際の関数, 実行後にfreeするための関数。
先頭と最後はいじらなくていいので、真ん中だけそれっぽい名前にいじる(もちろん名前は変えなくてもいい)


$ vim mysql_casual.cc
..
mysql_declare_plugin(rewrite_example)
{
  MYSQL_REWRITE_PRE_PARSE_PLUGIN,
  &rewrite_example_descriptor,
  "do_not_allow_kuso_kuery",
  "yoku0825",
  "Making your f**king query to be syntax error :)",
  PLUGIN_LICENSE_GPL,
  rewrite_plugin_init,
  NULL,
  0x0001,                                       /* version 0.0.1      */
  NULL,                                         /* status variables   */
  NULL,                                         /* system variables   */
  NULL,                                         /* config options     */
  0,                                            /* flqgs              */
}
mysql_declare_plugin_end;

i_sぷらぎんとか書いているとおなじみの、mysql_declare_plugin構造体。説明文とかAUTHORをちょろっといじる。


$ vim mysql_casual.cc
..
static int your_query_is_fxxking_dude(Mysql_rewrite_pre_parse_param *param)
{
  unsigned depth= 0, max_depth= 0;

  for (unsigned i= 0; i < param->query_length; i++)
  {
    if (param->query[i] == '(')
    {
      depth++;
      if (depth > max_depth)
        max_depth= depth;
    }
    else if (param->query[i] == ')')
      depth--;
  }

  if (max_depth > 2)
  {
    param->rewritten_query= strdup("Your query is f**king!!");
    param->rewritten_query_length= strlen("Your query is f**king!!");
    param->flags|= FLAG_REWRITE_PLUGIN_QUERY_REWRITTEN;
  }
  else
  {
    param->rewritten_query= new char[param->query_length + 1];
    param->rewritten_query_length= param->query_length;
    strncpy(param->rewritten_query, param->query, param->query_length + 1);
  }

  return 0;
}
..

オリジナルではrewrite_lower関数になっているやつ(書き換えの本体)をいじくります。Mysql_rewrite_pre_parse_paramの構造体については include/mysql/plugin_query_rewrite.h に定義があるので、そこを見ればなんとなくわかるかと。param->queryにもともとのクエリーが入っていて、param->rewritten_queryに変換後のクエリーが入る感じですね。カッコのネストの深さを数えて、一定以上だったら書き換え、そうでなければそのまま通すような関数にしました。


$ cmake -DDOWNLOAD_BOOST=1 -DWITH_BOOST=/tmp/my_boost
$ make
$ sudo make install

長いのでお茶でも淹れてきましょうかね。。


$ cd /usr/local/mysql
$ sudo useradd mysql
$ sudo ./bin/mysql_install_db --user=mysql --datadir=./data --basedir=./ --insecure
$ sudo chown -R mysql. /usr/local/mysql/data
$ sudo ./bin/mysqld_safe &
$ bin/mysql -uroot

5.7.5からmysql_install_dbはbinの下に移動になったんですよね。あと、basedirを与えないとエラーになって怒る。--insecureはデフォルトのランダムパスワードを設定しないという待望のオプションです。


mysql> INSTALL PLUGIN do_not_allow_kuso_query SONAME 'mysql_casual.so';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> SELECT (1);
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> SELECT ((1));
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> SELECT (((1)));
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Your query is f**king!!' at line 1

mysql> SHOW WARNINGS;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                   |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'SELECT (((1)))' rewritten to 'Your query is f**king!!' by plugin: do_not_allow_kuso_query.                                                                         |
| Error | 1064 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Your query is f**king!!' at line 1 |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

はい、出来上がりです! とてもカジュアルでしたね。明日からきっと雨後のたけのこのようににょきにょきとクエリーリライトぷらぎんが現れることでしょう。

寒い日が続くようですが、みなさまお風邪など召しませんように。

明日は @karupanerura さんです!

0 件のコメント :

コメントを投稿