この記事は MySQL Casual Advent Calendar 2014の3日目の記事です。
クソクエリーについての名言がつい先週生まれたばかりですが、みなさま如何お過ごしでしょうか。そういえば今年は Kuso-query As A Code みたいな話もさせてもらいました。
過去、現在、未来、全宇宙に存在する全てのクソクエリーを、生まれる前に自分の手でカジュアルに消し去るため(仮)に、MySQL 5.7.5-labsのQuery Rewrite Pluginの記事では触れるだけだったオレオレrewriteプラグインを書いてみました。君のクエリにレボ☆リューション! (仮)
どういう動作をさせるかというと、
シンプルな話、クソっぽいクエリー(今実装してあるのは、かっこが3つ以上ネストしているもの)が来た時に強制的にクエリーを上書きします。そしてこの文字列はSQLとして成立していないので、 *必ず*シンタックスエラーになります。
パーサーのレイヤーで書き換えているので、スローログとかもこの通り。
これでもう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ががんばってリトライしてくれるので、それほど困ってませんが。
Write Yourself a Query Rewrite Plugin: Part 1 | MySQL Server Blog から読み取るに、プラグインそのもののソースと"plug.in"ファイル、CMakeLists.txtを放り込んでやればいいらしい。
というわけで公式のrewrite_exampleをスケルトンにして実装していく。まずはCMakeLists.txtでこれは難しくない。i_sぷらぎんとかと同じで元になるソースの名前とモジュールの名前を決めてやるだけ。
"plug.in"も同じ感じだった。ダイナミックリンクするときのファイル名と、スタティックリンクする時のファイル名を指定する? (i_sぷらぎんの時には書かなかったなこれ)
で、核になるリライトぷらぎん本体。Query rewrite用にAPIが切ってあるので、それを呼ぶ感じで実装していく。やっぱりrewrite_example.ccをリネームしてそこを直してみるのが早い。
先頭からQuery Rewrite用APIのバージョン, クエリーのリライトに使う実際の関数, 実行後にfreeするための関数。
先頭と最後はいじらなくていいので、真ん中だけそれっぽい名前にいじる(もちろん名前は変えなくてもいい)
i_sぷらぎんとか書いているとおなじみの、mysql_declare_plugin構造体。説明文とかAUTHORをちょろっといじる。
オリジナルではrewrite_lower関数になっているやつ(書き換えの本体)をいじくります。Mysql_rewrite_pre_parse_paramの構造体については include/mysql/plugin_query_rewrite.h に定義があるので、そこを見ればなんとなくわかるかと。param->queryにもともとのクエリーが入っていて、param->rewritten_queryに変換後のクエリーが入る感じですね。カッコのネストの深さを数えて、一定以上だったら書き換え、そうでなければそのまま通すような関数にしました。
長いのでお茶でも淹れてきましょうかね。。
5.7.5からmysql_install_dbはbinの下に移動になったんですよね。あと、basedirを与えないとエラーになって怒る。--insecureはデフォルトのランダムパスワードを設定しないという待望のオプションです。
はい、出来上がりです! とてもカジュアルでしたね。明日からきっと雨後のたけのこのようににょきにょきとクエリーリライトぷらぎんが現れることでしょう。
寒い日が続くようですが、みなさまお風邪など召しませんように。
明日は @karupaneruraさんです!
クソクエリーについての名言がつい先週生まれたばかりですが、みなさま如何お過ごしでしょうか。そういえば今年は 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さんです!